linux/scripts/sphinx-pre-install
<<
>>
Prefs
   1#!/usr/bin/perl
   2# SPDX-License-Identifier: GPL-2.0-or-later
   3use strict;
   4
   5# Copyright (c) 2017 Mauro Carvalho Chehab <mchehab@kernel.org>
   6#
   7
   8my $conf = "Documentation/conf.py";
   9my $requirement_file = "Documentation/sphinx/requirements.txt";
  10
  11#
  12# Static vars
  13#
  14
  15my %missing;
  16my $system_release;
  17my $need = 0;
  18my $optional = 0;
  19my $need_symlink = 0;
  20my $need_sphinx = 0;
  21my $rec_sphinx_upgrade = 0;
  22my $install = "";
  23my $virtenv_dir = "sphinx_";
  24
  25#
  26# Command line arguments
  27#
  28
  29my $pdf = 1;
  30my $virtualenv = 1;
  31
  32#
  33# List of required texlive packages on Fedora and OpenSuse
  34#
  35
  36my %texlive = (
  37        'amsfonts.sty'       => 'texlive-amsfonts',
  38        'amsmath.sty'        => 'texlive-amsmath',
  39        'amssymb.sty'        => 'texlive-amsfonts',
  40        'amsthm.sty'         => 'texlive-amscls',
  41        'anyfontsize.sty'    => 'texlive-anyfontsize',
  42        'atbegshi.sty'       => 'texlive-oberdiek',
  43        'bm.sty'             => 'texlive-tools',
  44        'capt-of.sty'        => 'texlive-capt-of',
  45        'cmap.sty'           => 'texlive-cmap',
  46        'ecrm1000.tfm'       => 'texlive-ec',
  47        'eqparbox.sty'       => 'texlive-eqparbox',
  48        'eu1enc.def'         => 'texlive-euenc',
  49        'fancybox.sty'       => 'texlive-fancybox',
  50        'fancyvrb.sty'       => 'texlive-fancyvrb',
  51        'float.sty'          => 'texlive-float',
  52        'fncychap.sty'       => 'texlive-fncychap',
  53        'footnote.sty'       => 'texlive-mdwtools',
  54        'framed.sty'         => 'texlive-framed',
  55        'luatex85.sty'       => 'texlive-luatex85',
  56        'multirow.sty'       => 'texlive-multirow',
  57        'needspace.sty'      => 'texlive-needspace',
  58        'palatino.sty'       => 'texlive-psnfss',
  59        'parskip.sty'        => 'texlive-parskip',
  60        'polyglossia.sty'    => 'texlive-polyglossia',
  61        'tabulary.sty'       => 'texlive-tabulary',
  62        'threeparttable.sty' => 'texlive-threeparttable',
  63        'titlesec.sty'       => 'texlive-titlesec',
  64        'ucs.sty'            => 'texlive-ucs',
  65        'upquote.sty'        => 'texlive-upquote',
  66        'wrapfig.sty'        => 'texlive-wrapfig',
  67);
  68
  69#
  70# Subroutines that checks if a feature exists
  71#
  72
  73sub check_missing(%)
  74{
  75        my %map = %{$_[0]};
  76
  77        foreach my $prog (sort keys %missing) {
  78                my $is_optional = $missing{$prog};
  79
  80                if ($is_optional) {
  81                        print "Warning: better to also install \"$prog\".\n";
  82                } else {
  83                        print "ERROR: please install \"$prog\", otherwise, build won't work.\n";
  84                }
  85                if (defined($map{$prog})) {
  86                        $install .= " " . $map{$prog};
  87                } else {
  88                        $install .= " " . $prog;
  89                }
  90        }
  91
  92        $install =~ s/^\s//;
  93}
  94
  95sub add_package($$)
  96{
  97        my $package = shift;
  98        my $is_optional = shift;
  99
 100        $missing{$package} = $is_optional;
 101        if ($is_optional) {
 102                $optional++;
 103        } else {
 104                $need++;
 105        }
 106}
 107
 108sub check_missing_file($$$)
 109{
 110        my $file = shift;
 111        my $package = shift;
 112        my $is_optional = shift;
 113
 114        return if(-e $file);
 115
 116        add_package($package, $is_optional);
 117}
 118
 119sub findprog($)
 120{
 121        foreach(split(/:/, $ENV{PATH})) {
 122                return "$_/$_[0]" if(-x "$_/$_[0]");
 123        }
 124}
 125
 126sub check_program($$)
 127{
 128        my $prog = shift;
 129        my $is_optional = shift;
 130
 131        return if findprog($prog);
 132
 133        add_package($prog, $is_optional);
 134}
 135
 136sub check_perl_module($$)
 137{
 138        my $prog = shift;
 139        my $is_optional = shift;
 140
 141        my $err = system("perl -M$prog -e 1 2>/dev/null /dev/null");
 142        return if ($err == 0);
 143
 144        add_package($prog, $is_optional);
 145}
 146
 147sub check_python_module($$)
 148{
 149        my $prog = shift;
 150        my $is_optional = shift;
 151
 152        my $err = system("python3 -c 'import $prog' 2>/dev/null /dev/null");
 153        return if ($err == 0);
 154        my $err = system("python -c 'import $prog' 2>/dev/null /dev/null");
 155        return if ($err == 0);
 156
 157        add_package($prog, $is_optional);
 158}
 159
 160sub check_rpm_missing($$)
 161{
 162        my @pkgs = @{$_[0]};
 163        my $is_optional = $_[1];
 164
 165        foreach my $prog(@pkgs) {
 166                my $err = system("rpm -q '$prog' 2>/dev/null >/dev/null");
 167                add_package($prog, $is_optional) if ($err);
 168        }
 169}
 170
 171sub check_pacman_missing($$)
 172{
 173        my @pkgs = @{$_[0]};
 174        my $is_optional = $_[1];
 175
 176        foreach my $prog(@pkgs) {
 177                my $err = system("pacman -Q '$prog' 2>/dev/null >/dev/null");
 178                add_package($prog, $is_optional) if ($err);
 179        }
 180}
 181
 182sub check_missing_tex($)
 183{
 184        my $is_optional = shift;
 185        my $kpsewhich = findprog("kpsewhich");
 186
 187        foreach my $prog(keys %texlive) {
 188                my $package = $texlive{$prog};
 189                if (!$kpsewhich) {
 190                        add_package($package, $is_optional);
 191                        next;
 192                }
 193                my $file = qx($kpsewhich $prog);
 194                add_package($package, $is_optional) if ($file =~ /^\s*$/);
 195        }
 196}
 197
 198sub get_sphinx_fname()
 199{
 200        my $fname = "sphinx-build";
 201        return $fname if findprog($fname);
 202
 203        $fname = "sphinx-build-3";
 204        if (findprog($fname)) {
 205                $need_symlink = 1;
 206                return $fname;
 207        }
 208
 209        if ($virtualenv) {
 210                my $prog = findprog("virtualenv-3");
 211                $prog = findprog("virtualenv-3.5") if (!$prog);
 212
 213                check_program("virtualenv", 0) if (!$prog);
 214                $need_sphinx = 1;
 215        } else {
 216                add_package("python-sphinx", 0);
 217        }
 218
 219        return "";
 220}
 221
 222sub check_sphinx()
 223{
 224        my $min_version;
 225        my $rec_version;
 226        my $cur_version;
 227
 228        open IN, $conf or die "Can't open $conf";
 229        while (<IN>) {
 230                if (m/^\s*needs_sphinx\s*=\s*[\'\"]([\d\.]+)[\'\"]/) {
 231                        $min_version=$1;
 232                        last;
 233                }
 234        }
 235        close IN;
 236
 237        die "Can't get needs_sphinx version from $conf" if (!$min_version);
 238
 239        open IN, $requirement_file or die "Can't open $requirement_file";
 240        while (<IN>) {
 241                if (m/^\s*Sphinx\s*==\s*([\d\.]+)$/) {
 242                        $rec_version=$1;
 243                        last;
 244                }
 245        }
 246        close IN;
 247
 248        die "Can't get recommended sphinx version from $requirement_file" if (!$min_version);
 249
 250        $virtenv_dir .= $rec_version;
 251
 252        my $sphinx = get_sphinx_fname();
 253        return if ($sphinx eq "");
 254
 255        open IN, "$sphinx --version 2>&1 |" or die "$sphinx returned an error";
 256        while (<IN>) {
 257                if (m/^\s*sphinx-build\s+([\d\.]+)$/) {
 258                        $cur_version=$1;
 259                        last;
 260                }
 261                # Sphinx 1.2.x uses a different format
 262                if (m/^\s*Sphinx.*\s+([\d\.]+)$/) {
 263                        $cur_version=$1;
 264                        last;
 265                }
 266        }
 267        close IN;
 268
 269        die "$sphinx didn't return its version" if (!$cur_version);
 270
 271        printf "Sphinx version %s (minimal: %s, recommended >= %s)\n",
 272                $cur_version, $min_version, $rec_version;
 273
 274        if ($cur_version lt $min_version) {
 275                print "Warning: Sphinx version should be >= $min_version\n\n";
 276                $need_sphinx = 1;
 277                return;
 278        }
 279
 280        if ($cur_version lt $rec_version) {
 281                print "Warning: It is recommended at least Sphinx version $rec_version.\n";
 282                print "         To upgrade, use:\n\n";
 283                $rec_sphinx_upgrade = 1;
 284        }
 285}
 286
 287#
 288# Ancillary subroutines
 289#
 290
 291sub catcheck($)
 292{
 293  my $res = "";
 294  $res = qx(cat $_[0]) if (-r $_[0]);
 295  return $res;
 296}
 297
 298sub which($)
 299{
 300        my $file = shift;
 301        my @path = split ":", $ENV{PATH};
 302
 303        foreach my $dir(@path) {
 304                my $name = $dir.'/'.$file;
 305                return $name if (-x $name );
 306        }
 307        return undef;
 308}
 309
 310#
 311# Subroutines that check distro-specific hints
 312#
 313
 314sub give_debian_hints()
 315{
 316        my %map = (
 317                "python-sphinx"         => "python3-sphinx",
 318                "sphinx_rtd_theme"      => "python3-sphinx-rtd-theme",
 319                "virtualenv"            => "virtualenv",
 320                "dot"                   => "graphviz",
 321                "convert"               => "imagemagick",
 322                "Pod::Usage"            => "perl-modules",
 323                "xelatex"               => "texlive-xetex",
 324                "rsvg-convert"          => "librsvg2-bin",
 325        );
 326
 327        if ($pdf) {
 328                check_missing_file("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
 329                                   "fonts-dejavu", 1);
 330        }
 331
 332        check_program("dvipng", 1) if ($pdf);
 333        check_missing(\%map);
 334
 335        return if (!$need && !$optional);
 336        printf("You should run:\n\n\tsudo apt-get install $install\n");
 337}
 338
 339sub give_redhat_hints()
 340{
 341        my %map = (
 342                "python-sphinx"         => "python3-sphinx",
 343                "sphinx_rtd_theme"      => "python3-sphinx_rtd_theme",
 344                "virtualenv"            => "python3-virtualenv",
 345                "dot"                   => "graphviz",
 346                "convert"               => "ImageMagick",
 347                "Pod::Usage"            => "perl-Pod-Usage",
 348                "xelatex"               => "texlive-xetex-bin",
 349                "rsvg-convert"          => "librsvg2-tools",
 350        );
 351
 352        my @fedora26_opt_pkgs = (
 353                "graphviz-gd",          # Fedora 26: needed for PDF support
 354        );
 355
 356        my @fedora_tex_pkgs = (
 357                "texlive-collection-fontsrecommended",
 358                "texlive-collection-latex",
 359                "dejavu-sans-fonts",
 360                "dejavu-serif-fonts",
 361                "dejavu-sans-mono-fonts",
 362        );
 363
 364        #
 365        # Checks valid for RHEL/CentOS version 7.x.
 366        #
 367        if (! $system_release =~ /Fedora/) {
 368                $map{"virtualenv"} = "python-virtualenv";
 369        }
 370
 371        my $release;
 372
 373        $release = $1 if ($system_release =~ /Fedora\s+release\s+(\d+)/);
 374
 375        check_rpm_missing(\@fedora26_opt_pkgs, 1) if ($pdf && $release >= 26);
 376        check_rpm_missing(\@fedora_tex_pkgs, 1) if ($pdf);
 377        check_missing_tex(1) if ($pdf);
 378        check_missing(\%map);
 379
 380        return if (!$need && !$optional);
 381
 382        if ($release >= 18) {
 383                # dnf, for Fedora 18+
 384                printf("You should run:\n\n\tsudo dnf install -y $install\n");
 385        } else {
 386                # yum, for RHEL (and clones) or Fedora version < 18
 387                printf("You should run:\n\n\tsudo yum install -y $install\n");
 388        }
 389}
 390
 391sub give_opensuse_hints()
 392{
 393        my %map = (
 394                "python-sphinx"         => "python3-sphinx",
 395                "sphinx_rtd_theme"      => "python3-sphinx_rtd_theme",
 396                "virtualenv"            => "python3-virtualenv",
 397                "dot"                   => "graphviz",
 398                "convert"               => "ImageMagick",
 399                "Pod::Usage"            => "perl-Pod-Usage",
 400                "xelatex"               => "texlive-xetex-bin",
 401                "rsvg-convert"          => "rsvg-view",
 402        );
 403
 404        my @suse_tex_pkgs = (
 405                "texlive-babel-english",
 406                "texlive-caption",
 407                "texlive-colortbl",
 408                "texlive-courier",
 409                "texlive-dvips",
 410                "texlive-helvetic",
 411                "texlive-makeindex",
 412                "texlive-metafont",
 413                "texlive-metapost",
 414                "texlive-palatino",
 415                "texlive-preview",
 416                "texlive-times",
 417                "texlive-zapfchan",
 418                "texlive-zapfding",
 419        );
 420
 421        check_rpm_missing(\@suse_tex_pkgs, 1) if ($pdf);
 422        check_missing_tex(1) if ($pdf);
 423        check_missing(\%map);
 424
 425        return if (!$need && !$optional);
 426        printf("You should run:\n\n\tsudo zypper install --no-recommends $install\n");
 427}
 428
 429sub give_mageia_hints()
 430{
 431        my %map = (
 432                "python-sphinx"         => "python3-sphinx",
 433                "sphinx_rtd_theme"      => "python3-sphinx_rtd_theme",
 434                "virtualenv"            => "python3-virtualenv",
 435                "dot"                   => "graphviz",
 436                "convert"               => "ImageMagick",
 437                "Pod::Usage"            => "perl-Pod-Usage",
 438                "xelatex"               => "texlive",
 439                "rsvg-convert"          => "librsvg2-tools",
 440        );
 441
 442        my @tex_pkgs = (
 443                "texlive-fontsextra",
 444        );
 445
 446        check_rpm_missing(\@tex_pkgs, 1) if ($pdf);
 447        check_missing(\%map);
 448
 449        return if (!$need && !$optional);
 450        printf("You should run:\n\n\tsudo urpmi $install\n");
 451}
 452
 453sub give_arch_linux_hints()
 454{
 455        my %map = (
 456                "sphinx_rtd_theme"      => "python-sphinx_rtd_theme",
 457                "virtualenv"            => "python-virtualenv",
 458                "dot"                   => "graphviz",
 459                "convert"               => "imagemagick",
 460                "xelatex"               => "texlive-bin",
 461                "rsvg-convert"          => "extra/librsvg",
 462        );
 463
 464        my @archlinux_tex_pkgs = (
 465                "texlive-core",
 466                "texlive-latexextra",
 467                "ttf-dejavu",
 468        );
 469        check_pacman_missing(\@archlinux_tex_pkgs, 1) if ($pdf);
 470        check_missing(\%map);
 471
 472        return if (!$need && !$optional);
 473        printf("You should run:\n\n\tsudo pacman -S $install\n");
 474}
 475
 476sub give_gentoo_hints()
 477{
 478        my %map = (
 479                "sphinx_rtd_theme"      => "dev-python/sphinx_rtd_theme",
 480                "virtualenv"            => "dev-python/virtualenv",
 481                "dot"                   => "media-gfx/graphviz",
 482                "convert"               => "media-gfx/imagemagick",
 483                "xelatex"               => "dev-texlive/texlive-xetex media-fonts/dejavu",
 484                "rsvg-convert"          => "gnome-base/librsvg",
 485        );
 486
 487        check_missing_file("/usr/share/fonts/dejavu/DejaVuSans.ttf",
 488                           "media-fonts/dejavu", 1) if ($pdf);
 489
 490        check_missing(\%map);
 491
 492        return if (!$need && !$optional);
 493
 494        printf("You should run:\n\n");
 495        printf("\tsudo su -c 'echo \"media-gfx/imagemagick svg png\" > /etc/portage/package.use/imagemagick'\n");
 496        printf("\tsudo su -c 'echo \"media-gfx/graphviz cairo pdf\" > /etc/portage/package.use/graphviz'\n");
 497        printf("\tsudo emerge --ask $install\n");
 498
 499}
 500
 501sub check_distros()
 502{
 503        # Distro-specific hints
 504        if ($system_release =~ /Red Hat Enterprise Linux/) {
 505                give_redhat_hints;
 506                return;
 507        }
 508        if ($system_release =~ /CentOS/) {
 509                give_redhat_hints;
 510                return;
 511        }
 512        if ($system_release =~ /Scientific Linux/) {
 513                give_redhat_hints;
 514                return;
 515        }
 516        if ($system_release =~ /Oracle Linux Server/) {
 517                give_redhat_hints;
 518                return;
 519        }
 520        if ($system_release =~ /Fedora/) {
 521                give_redhat_hints;
 522                return;
 523        }
 524        if ($system_release =~ /Ubuntu/) {
 525                give_debian_hints;
 526                return;
 527        }
 528        if ($system_release =~ /Debian/) {
 529                give_debian_hints;
 530                return;
 531        }
 532        if ($system_release =~ /openSUSE/) {
 533                give_opensuse_hints;
 534                return;
 535        }
 536        if ($system_release =~ /Mageia/) {
 537                give_mageia_hints;
 538                return;
 539        }
 540        if ($system_release =~ /Arch Linux/) {
 541                give_arch_linux_hints;
 542                return;
 543        }
 544        if ($system_release =~ /Gentoo/) {
 545                give_gentoo_hints;
 546                return;
 547        }
 548
 549        #
 550        # Fall-back to generic hint code for other distros
 551        # That's far from ideal, specially for LaTeX dependencies.
 552        #
 553        my %map = (
 554                "sphinx-build" => "sphinx"
 555        );
 556        check_missing_tex(1) if ($pdf);
 557        check_missing(\%map);
 558        print "I don't know distro $system_release.\n";
 559        print "So, I can't provide you a hint with the install procedure.\n";
 560        print "There are likely missing dependencies.\n";
 561}
 562
 563#
 564# Common dependencies
 565#
 566
 567sub check_needs()
 568{
 569        if ($system_release) {
 570                print "Detected OS: $system_release.\n";
 571        } else {
 572                print "Unknown OS\n";
 573        }
 574
 575        # RHEL 7.x and clones have Sphinx version 1.1.x and incomplete texlive
 576        if (($system_release =~ /Red Hat Enterprise Linux/) ||
 577            ($system_release =~ /CentOS/) ||
 578            ($system_release =~ /Scientific Linux/) ||
 579            ($system_release =~ /Oracle Linux Server/)) {
 580                $virtualenv = 1;
 581                $pdf = 0;
 582
 583                printf("NOTE: On this distro, Sphinx and TexLive shipped versions are incompatible\n");
 584                printf("with doc build. So, use Sphinx via a Python virtual environment.\n\n");
 585                printf("This script can't install a TexLive version that would provide PDF.\n");
 586        }
 587
 588        # Check for needed programs/tools
 589        check_sphinx();
 590        check_perl_module("Pod::Usage", 0);
 591        check_program("make", 0);
 592        check_program("gcc", 0);
 593        check_python_module("sphinx_rtd_theme", 1) if (!$virtualenv);
 594        check_program("xelatex", 1) if ($pdf);
 595        check_program("dot", 1);
 596        check_program("convert", 1);
 597        check_program("rsvg-convert", 1) if ($pdf);
 598        check_program("latexmk", 1) if ($pdf);
 599
 600        check_distros();
 601
 602        if ($need_symlink) {
 603                printf "\tsudo ln -sf %s /usr/bin/sphinx-build\n\n",
 604                       which("sphinx-build-3");
 605        }
 606        if ($need_sphinx || $rec_sphinx_upgrade) {
 607                my $activate = "$virtenv_dir/bin/activate";
 608                if (-e "$ENV{'PWD'}/$activate") {
 609                        printf "\nNeed to activate virtualenv with:\n";
 610                        printf "\t. $activate\n";
 611                } else {
 612                        my $virtualenv = findprog("virtualenv-3");
 613                        $virtualenv = findprog("virtualenv-3.5") if (!$virtualenv);
 614                        $virtualenv = findprog("virtualenv") if (!$virtualenv);
 615                        $virtualenv = "virtualenv" if (!$virtualenv);
 616
 617                        printf "\t$virtualenv $virtenv_dir\n";
 618                        printf "\t. $activate\n";
 619                        printf "\tpip install -r $requirement_file\n";
 620
 621                        $need++ if (!$rec_sphinx_upgrade);
 622                }
 623        }
 624        printf "\n";
 625
 626        print "All optional dependenties are met.\n" if (!$optional);
 627
 628        if ($need == 1) {
 629                die "Can't build as $need mandatory dependency is missing";
 630        } elsif ($need) {
 631                die "Can't build as $need mandatory dependencies are missing";
 632        }
 633
 634        print "Needed package dependencies are met.\n";
 635}
 636
 637#
 638# Main
 639#
 640
 641while (@ARGV) {
 642        my $arg = shift(@ARGV);
 643
 644        if ($arg eq "--no-virtualenv") {
 645                $virtualenv = 0;
 646        } elsif ($arg eq "--no-pdf"){
 647                $pdf = 0;
 648        } else {
 649                print "Usage:\n\t$0 <--no-virtualenv> <--no-pdf>\n\n";
 650                exit -1;
 651        }
 652}
 653
 654#
 655# Determine the system type. There's no standard unique way that would
 656# work with all distros with a minimal package install. So, several
 657# methods are used here.
 658#
 659# By default, it will use lsb_release function. If not available, it will
 660# fail back to reading the known different places where the distro name
 661# is stored
 662#
 663
 664$system_release = qx(lsb_release -d) if which("lsb_release");
 665$system_release =~ s/Description:\s*// if ($system_release);
 666$system_release = catcheck("/etc/system-release") if !$system_release;
 667$system_release = catcheck("/etc/redhat-release") if !$system_release;
 668$system_release = catcheck("/etc/lsb-release") if !$system_release;
 669$system_release = catcheck("/etc/gentoo-release") if !$system_release;
 670$system_release = catcheck("/etc/issue") if !$system_release;
 671$system_release =~ s/\s+$//;
 672
 673check_needs;
 674