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