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