xref: /openbmc/linux/scripts/sphinx-pre-install (revision 82c29810)
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