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