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