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