1#!/usr/bin/perl 2use strict; 3 4# Copyright (c) 2017 Mauro Carvalho Chehab <mchehab@kernel.org> 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 $virtenv_dir = "sphinx_1.4"; 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 $install = ""; 30 31# 32# Command line arguments 33# 34 35my $pdf = 1; 36my $virtualenv = 1; 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 check_sphinx() 205{ 206 return if findprog("sphinx-build"); 207 208 if (findprog("sphinx-build-3")) { 209 $need_symlink = 1; 210 return; 211 } 212 213 if ($virtualenv) { 214 my $prog = findprog("virtualenv-3"); 215 $prog = findprog("virtualenv-3.5") if (!$prog); 216 217 check_program("virtualenv", 0) if (!$prog); 218 $need_sphinx = 1; 219 } else { 220 add_package("python-sphinx", 0); 221 } 222} 223 224# 225# Ancillary subroutines 226# 227 228sub catcheck($) 229{ 230 my $res = ""; 231 $res = qx(cat $_[0]) if (-r $_[0]); 232 return $res; 233} 234 235sub which($) 236{ 237 my $file = shift; 238 my @path = split ":", $ENV{PATH}; 239 240 foreach my $dir(@path) { 241 my $name = $dir.'/'.$file; 242 return $name if (-x $name ); 243 } 244 return undef; 245} 246 247# 248# Subroutines that check distro-specific hints 249# 250 251sub give_debian_hints() 252{ 253 my %map = ( 254 "python-sphinx" => "python3-sphinx", 255 "sphinx_rtd_theme" => "python3-sphinx-rtd-theme", 256 "virtualenv" => "virtualenv", 257 "dot" => "graphviz", 258 "convert" => "imagemagick", 259 "Pod::Usage" => "perl-modules", 260 "xelatex" => "texlive-xetex", 261 "rsvg-convert" => "librsvg2-bin", 262 ); 263 264 if ($pdf) { 265 check_missing_file("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 266 "fonts-dejavu", 1); 267 } 268 269 check_program("dvipng", 1) if ($pdf); 270 check_missing(\%map); 271 272 return if (!$need && !$optional); 273 printf("You should run:\n\n\tsudo apt-get install $install\n"); 274} 275 276sub give_redhat_hints() 277{ 278 my %map = ( 279 "python-sphinx" => "python3-sphinx", 280 "sphinx_rtd_theme" => "python3-sphinx_rtd_theme", 281 "virtualenv" => "python3-virtualenv", 282 "dot" => "graphviz", 283 "convert" => "ImageMagick", 284 "Pod::Usage" => "perl-Pod-Usage", 285 "xelatex" => "texlive-xetex-bin", 286 "rsvg-convert" => "librsvg2-tools", 287 ); 288 289 my @fedora26_opt_pkgs = ( 290 "graphviz-gd", # Fedora 26: needed for PDF support 291 ); 292 293 my @fedora_tex_pkgs = ( 294 "texlive-collection-fontsrecommended", 295 "texlive-collection-latex", 296 "dejavu-sans-fonts", 297 "dejavu-serif-fonts", 298 "dejavu-sans-mono-fonts", 299 ); 300 301 # 302 # Checks valid for RHEL/CentOS version 7.x. 303 # 304 if (! $system_release =~ /Fedora/) { 305 $map{"virtualenv"} = "python-virtualenv"; 306 } 307 308 my $release; 309 310 $release = $1 if ($system_release =~ /Fedora\s+release\s+(\d+)/); 311 312 check_rpm_missing(\@fedora26_opt_pkgs, 1) if ($pdf && $release >= 26); 313 check_rpm_missing(\@fedora_tex_pkgs, 1) if ($pdf); 314 check_missing_tex(1) if ($pdf); 315 check_missing(\%map); 316 317 return if (!$need && !$optional); 318 319 if ($release >= 18) { 320 # dnf, for Fedora 18+ 321 printf("You should run:\n\n\tsudo dnf install -y $install\n"); 322 } else { 323 # yum, for RHEL (and clones) or Fedora version < 18 324 printf("You should run:\n\n\tsudo yum install -y $install\n"); 325 } 326} 327 328sub give_opensuse_hints() 329{ 330 my %map = ( 331 "python-sphinx" => "python3-sphinx", 332 "sphinx_rtd_theme" => "python3-sphinx_rtd_theme", 333 "virtualenv" => "python3-virtualenv", 334 "dot" => "graphviz", 335 "convert" => "ImageMagick", 336 "Pod::Usage" => "perl-Pod-Usage", 337 "xelatex" => "texlive-xetex-bin", 338 "rsvg-convert" => "rsvg-view", 339 ); 340 341 my @suse_tex_pkgs = ( 342 "texlive-babel-english", 343 "texlive-caption", 344 "texlive-colortbl", 345 "texlive-courier", 346 "texlive-dvips", 347 "texlive-helvetic", 348 "texlive-makeindex", 349 "texlive-metafont", 350 "texlive-metapost", 351 "texlive-palatino", 352 "texlive-preview", 353 "texlive-times", 354 "texlive-zapfchan", 355 "texlive-zapfding", 356 ); 357 358 check_rpm_missing(\@suse_tex_pkgs, 1) if ($pdf); 359 check_missing_tex(1) if ($pdf); 360 check_missing(\%map); 361 362 return if (!$need && !$optional); 363 printf("You should run:\n\n\tsudo zypper install --no-recommends $install\n"); 364} 365 366sub give_mageia_hints() 367{ 368 my %map = ( 369 "python-sphinx" => "python3-sphinx", 370 "sphinx_rtd_theme" => "python3-sphinx_rtd_theme", 371 "virtualenv" => "python3-virtualenv", 372 "dot" => "graphviz", 373 "convert" => "ImageMagick", 374 "Pod::Usage" => "perl-Pod-Usage", 375 "xelatex" => "texlive", 376 "rsvg-convert" => "librsvg2-tools", 377 ); 378 379 my @tex_pkgs = ( 380 "texlive-fontsextra", 381 ); 382 383 check_rpm_missing(\@tex_pkgs, 1) if ($pdf); 384 check_missing(\%map); 385 386 return if (!$need && !$optional); 387 printf("You should run:\n\n\tsudo urpmi $install\n"); 388} 389 390sub give_arch_linux_hints() 391{ 392 my %map = ( 393 "sphinx_rtd_theme" => "python-sphinx_rtd_theme", 394 "virtualenv" => "python-virtualenv", 395 "dot" => "graphviz", 396 "convert" => "imagemagick", 397 "xelatex" => "texlive-bin", 398 "rsvg-convert" => "extra/librsvg", 399 ); 400 401 my @archlinux_tex_pkgs = ( 402 "texlive-core", 403 "texlive-latexextra", 404 "ttf-dejavu", 405 ); 406 check_pacman_missing(\@archlinux_tex_pkgs, 1) if ($pdf); 407 check_missing(\%map); 408 409 return if (!$need && !$optional); 410 printf("You should run:\n\n\tsudo pacman -S $install\n"); 411} 412 413sub give_gentoo_hints() 414{ 415 my %map = ( 416 "sphinx_rtd_theme" => "dev-python/sphinx_rtd_theme", 417 "virtualenv" => "dev-python/virtualenv", 418 "dot" => "media-gfx/graphviz", 419 "convert" => "media-gfx/imagemagick", 420 "xelatex" => "dev-texlive/texlive-xetex media-fonts/dejavu", 421 "rsvg-convert" => "gnome-base/librsvg", 422 ); 423 424 check_missing_file("/usr/share/fonts/dejavu/DejaVuSans.ttf", 425 "media-fonts/dejavu", 1) if ($pdf); 426 427 check_missing(\%map); 428 429 return if (!$need && !$optional); 430 431 printf("You should run:\n\n"); 432 printf("\tsudo su -c 'echo \"media-gfx/imagemagick svg png\" > /etc/portage/package.use/imagemagick'\n"); 433 printf("\tsudo su -c 'echo \"media-gfx/graphviz cairo pdf\" > /etc/portage/package.use/graphviz'\n"); 434 printf("\tsudo emerge --ask $install\n"); 435 436} 437 438sub check_distros() 439{ 440 # Distro-specific hints 441 if ($system_release =~ /Red Hat Enterprise Linux/) { 442 give_redhat_hints; 443 return; 444 } 445 if ($system_release =~ /CentOS/) { 446 give_redhat_hints; 447 return; 448 } 449 if ($system_release =~ /Scientific Linux/) { 450 give_redhat_hints; 451 return; 452 } 453 if ($system_release =~ /Oracle Linux Server/) { 454 give_redhat_hints; 455 return; 456 } 457 if ($system_release =~ /Fedora/) { 458 give_redhat_hints; 459 return; 460 } 461 if ($system_release =~ /Ubuntu/) { 462 give_debian_hints; 463 return; 464 } 465 if ($system_release =~ /Debian/) { 466 give_debian_hints; 467 return; 468 } 469 if ($system_release =~ /openSUSE/) { 470 give_opensuse_hints; 471 return; 472 } 473 if ($system_release =~ /Mageia/) { 474 give_mageia_hints; 475 return; 476 } 477 if ($system_release =~ /Arch Linux/) { 478 give_arch_linux_hints; 479 return; 480 } 481 if ($system_release =~ /Gentoo/) { 482 give_gentoo_hints; 483 return; 484 } 485 486 # 487 # Fall-back to generic hint code for other distros 488 # That's far from ideal, specially for LaTeX dependencies. 489 # 490 my %map = ( 491 "sphinx-build" => "sphinx" 492 ); 493 check_missing_tex(1) if ($pdf); 494 check_missing(\%map); 495 print "I don't know distro $system_release.\n"; 496 print "So, I can't provide you a hint with the install procedure.\n"; 497 print "There are likely missing dependencies.\n"; 498} 499 500# 501# Common dependencies 502# 503 504sub check_needs() 505{ 506 if ($system_release) { 507 print "Detected OS: $system_release.\n"; 508 } else { 509 print "Unknown OS\n"; 510 } 511 512 # RHEL 7.x and clones have Sphinx version 1.1.x and incomplete texlive 513 if (($system_release =~ /Red Hat Enterprise Linux/) || 514 ($system_release =~ /CentOS/) || 515 ($system_release =~ /Scientific Linux/) || 516 ($system_release =~ /Oracle Linux Server/)) { 517 $virtualenv = 1; 518 $pdf = 0; 519 520 printf("NOTE: On this distro, Sphinx and TexLive shipped versions are incompatible\n"); 521 printf("with doc build. So, use Sphinx via a Python virtual environment.\n\n"); 522 printf("This script can't install a TexLive version that would provide PDF.\n"); 523 } 524 525 # Check for needed programs/tools 526 check_sphinx(); 527 check_perl_module("Pod::Usage", 0); 528 check_program("make", 0); 529 check_program("gcc", 0); 530 check_python_module("sphinx_rtd_theme", 1) if (!$virtualenv); 531 check_program("xelatex", 1) if ($pdf); 532 check_program("dot", 1); 533 check_program("convert", 1); 534 check_program("rsvg-convert", 1) if ($pdf); 535 check_program("latexmk", 1) if ($pdf); 536 537 check_distros(); 538 539 if ($need_symlink) { 540 printf "\tsudo ln -sf %s /usr/bin/sphinx-build\n\n", 541 which("sphinx-build-3"); 542 } 543 if ($need_sphinx) { 544 my $activate = "$virtenv_dir/bin/activate"; 545 if (-e "$ENV{'PWD'}/$activate") { 546 printf "\nNeed to activate virtualenv with:\n"; 547 printf "\t. $activate\n"; 548 } else { 549 my $virtualenv = findprog("virtualenv-3"); 550 $virtualenv = findprog("virtualenv-3.5") if (!$virtualenv); 551 $virtualenv = findprog("virtualenv") if (!$virtualenv); 552 $virtualenv = "virtualenv" if (!$virtualenv); 553 554 printf "\t$virtualenv $virtenv_dir\n"; 555 printf "\t. $activate\n"; 556 printf "\tpip install -r $requirement_file\n"; 557 $need++; 558 } 559 } 560 printf "\n"; 561 562 print "All optional dependenties are met.\n" if (!$optional); 563 564 if ($need == 1) { 565 die "Can't build as $need mandatory dependency is missing"; 566 } elsif ($need) { 567 die "Can't build as $need mandatory dependencies are missing"; 568 } 569 570 print "Needed package dependencies are met.\n"; 571} 572 573# 574# Main 575# 576 577while (@ARGV) { 578 my $arg = shift(@ARGV); 579 580 if ($arg eq "--no-virtualenv") { 581 $virtualenv = 0; 582 } elsif ($arg eq "--no-pdf"){ 583 $pdf = 0; 584 } else { 585 print "Usage:\n\t$0 <--no-virtualenv> <--no-pdf>\n\n"; 586 exit -1; 587 } 588} 589 590# 591# Determine the system type. There's no standard unique way that would 592# work with all distros with a minimal package install. So, several 593# methods are used here. 594# 595# By default, it will use lsb_release function. If not available, it will 596# fail back to reading the known different places where the distro name 597# is stored 598# 599 600$system_release = qx(lsb_release -d) if which("lsb_release"); 601$system_release =~ s/Description:\s*// if ($system_release); 602$system_release = catcheck("/etc/system-release") if !$system_release; 603$system_release = catcheck("/etc/redhat-release") if !$system_release; 604$system_release = catcheck("/etc/lsb-release") if !$system_release; 605$system_release = catcheck("/etc/gentoo-release") if !$system_release; 606$system_release = catcheck("/etc/issue") if !$system_release; 607$system_release =~ s/\s+$//; 608 609check_needs; 610