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