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