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