xref: /openbmc/linux/scripts/sphinx-pre-install (revision a20eefae)
1#!/usr/bin/perl
2# SPDX-License-Identifier: GPL-2.0-or-later
3use strict;
4
5# Copyright (c) 2017-2019 Mauro Carvalho Chehab <mchehab@kernel.org>
6#
7
8my $prefix = "./";
9$prefix = "$ENV{'srctree'}/" if ($ENV{'srctree'});
10
11my $conf = $prefix . "Documentation/conf.py";
12my $requirement_file = $prefix . "Documentation/sphinx/requirements.txt";
13my $virtenv_prefix = "sphinx_";
14
15#
16# Static vars
17#
18
19my %missing;
20my $system_release;
21my $need = 0;
22my $optional = 0;
23my $need_symlink = 0;
24my $need_sphinx = 0;
25my $rec_sphinx_upgrade = 0;
26my $install = "";
27my $virtenv_dir = "";
28my $min_version;
29
30#
31# Command line arguments
32#
33
34my $pdf = 1;
35my $virtualenv = 1;
36my $version_check = 0;
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 get_sphinx_fname()
205{
206	my $fname = "sphinx-build";
207	return $fname if findprog($fname);
208
209	$fname = "sphinx-build-3";
210	if (findprog($fname)) {
211		$need_symlink = 1;
212		return $fname;
213	}
214
215	if ($virtualenv) {
216		my $prog = findprog("virtualenv-3");
217		$prog = findprog("virtualenv-3.5") if (!$prog);
218
219		check_program("virtualenv", 0) if (!$prog);
220		$need_sphinx = 1;
221	} else {
222		add_package("python-sphinx", 0);
223	}
224
225	return "";
226}
227
228sub check_sphinx()
229{
230	my $rec_version;
231	my $cur_version;
232
233	open IN, $conf or die "Can't open $conf";
234	while (<IN>) {
235		if (m/^\s*needs_sphinx\s*=\s*[\'\"]([\d\.]+)[\'\"]/) {
236			$min_version=$1;
237			last;
238		}
239	}
240	close IN;
241
242	die "Can't get needs_sphinx version from $conf" if (!$min_version);
243
244	open IN, $requirement_file or die "Can't open $requirement_file";
245	while (<IN>) {
246		if (m/^\s*Sphinx\s*==\s*([\d\.]+)$/) {
247			$rec_version=$1;
248			last;
249		}
250	}
251	close IN;
252
253	die "Can't get recommended sphinx version from $requirement_file" if (!$min_version);
254
255	$virtenv_dir = $virtenv_prefix . $rec_version;
256
257	my $sphinx = get_sphinx_fname();
258	return if ($sphinx eq "");
259
260	open IN, "$sphinx --version 2>&1 |" or die "$sphinx returned an error";
261	while (<IN>) {
262		if (m/^\s*sphinx-build\s+([\d\.]+)$/) {
263			$cur_version=$1;
264			last;
265		}
266		# Sphinx 1.2.x uses a different format
267		if (m/^\s*Sphinx.*\s+([\d\.]+)$/) {
268			$cur_version=$1;
269			last;
270		}
271	}
272	close IN;
273
274	die "$sphinx didn't return its version" if (!$cur_version);
275
276	if ($cur_version lt $min_version) {
277		printf "ERROR: Sphinx version is %s. It should be >= %s (recommended >= %s)\n",
278		       $cur_version, $min_version, $rec_version;;
279		$need_sphinx = 1;
280		return;
281	}
282
283	if ($cur_version lt $rec_version) {
284		printf "Sphinx version %s\n", $cur_version;
285		print "Warning: It is recommended at least Sphinx version $rec_version.\n";
286		$rec_sphinx_upgrade = 1;
287		return;
288	}
289
290	# On version check mode, just assume Sphinx has all mandatory deps
291	exit (0) if ($version_check);
292}
293
294#
295# Ancillary subroutines
296#
297
298sub catcheck($)
299{
300  my $res = "";
301  $res = qx(cat $_[0]) if (-r $_[0]);
302  return $res;
303}
304
305sub which($)
306{
307	my $file = shift;
308	my @path = split ":", $ENV{PATH};
309
310	foreach my $dir(@path) {
311		my $name = $dir.'/'.$file;
312		return $name if (-x $name );
313	}
314	return undef;
315}
316
317#
318# Subroutines that check distro-specific hints
319#
320
321sub give_debian_hints()
322{
323	my %map = (
324		"python-sphinx"		=> "python3-sphinx",
325		"sphinx_rtd_theme"	=> "python3-sphinx-rtd-theme",
326		"virtualenv"		=> "virtualenv",
327		"dot"			=> "graphviz",
328		"convert"		=> "imagemagick",
329		"Pod::Usage"		=> "perl-modules",
330		"xelatex"		=> "texlive-xetex",
331		"rsvg-convert"		=> "librsvg2-bin",
332	);
333
334	if ($pdf) {
335		check_missing_file("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
336				   "fonts-dejavu", 1);
337	}
338
339	check_program("dvipng", 1) if ($pdf);
340	check_missing(\%map);
341
342	return if (!$need && !$optional);
343	printf("You should run:\n\n\tsudo apt-get install $install\n");
344}
345
346sub give_redhat_hints()
347{
348	my %map = (
349		"python-sphinx"		=> "python3-sphinx",
350		"sphinx_rtd_theme"	=> "python3-sphinx_rtd_theme",
351		"virtualenv"		=> "python3-virtualenv",
352		"dot"			=> "graphviz",
353		"convert"		=> "ImageMagick",
354		"Pod::Usage"		=> "perl-Pod-Usage",
355		"xelatex"		=> "texlive-xetex-bin",
356		"rsvg-convert"		=> "librsvg2-tools",
357	);
358
359	my @fedora26_opt_pkgs = (
360		"graphviz-gd",		# Fedora 26: needed for PDF support
361	);
362
363	my @fedora_tex_pkgs = (
364		"texlive-collection-fontsrecommended",
365		"texlive-collection-latex",
366		"dejavu-sans-fonts",
367		"dejavu-serif-fonts",
368		"dejavu-sans-mono-fonts",
369	);
370
371	#
372	# Checks valid for RHEL/CentOS version 7.x.
373	#
374	if (! $system_release =~ /Fedora/) {
375		$map{"virtualenv"} = "python-virtualenv";
376	}
377
378	my $release;
379
380	$release = $1 if ($system_release =~ /Fedora\s+release\s+(\d+)/);
381
382	check_rpm_missing(\@fedora26_opt_pkgs, 1) if ($pdf && $release >= 26);
383	check_rpm_missing(\@fedora_tex_pkgs, 1) if ($pdf);
384	check_missing_tex(1) if ($pdf);
385	check_missing(\%map);
386
387	return if (!$need && !$optional);
388
389	if ($release >= 18) {
390		# dnf, for Fedora 18+
391		printf("You should run:\n\n\tsudo dnf install -y $install\n");
392	} else {
393		# yum, for RHEL (and clones) or Fedora version < 18
394		printf("You should run:\n\n\tsudo yum install -y $install\n");
395	}
396}
397
398sub give_opensuse_hints()
399{
400	my %map = (
401		"python-sphinx"		=> "python3-sphinx",
402		"sphinx_rtd_theme"	=> "python3-sphinx_rtd_theme",
403		"virtualenv"		=> "python3-virtualenv",
404		"dot"			=> "graphviz",
405		"convert"		=> "ImageMagick",
406		"Pod::Usage"		=> "perl-Pod-Usage",
407		"xelatex"		=> "texlive-xetex-bin",
408		"rsvg-convert"		=> "rsvg-view",
409	);
410
411	my @suse_tex_pkgs = (
412		"texlive-babel-english",
413		"texlive-caption",
414		"texlive-colortbl",
415		"texlive-courier",
416		"texlive-dvips",
417		"texlive-helvetic",
418		"texlive-makeindex",
419		"texlive-metafont",
420		"texlive-metapost",
421		"texlive-palatino",
422		"texlive-preview",
423		"texlive-times",
424		"texlive-zapfchan",
425		"texlive-zapfding",
426	);
427
428	check_rpm_missing(\@suse_tex_pkgs, 1) if ($pdf);
429	check_missing_tex(1) if ($pdf);
430	check_missing(\%map);
431
432	return if (!$need && !$optional);
433	printf("You should run:\n\n\tsudo zypper install --no-recommends $install\n");
434}
435
436sub give_mageia_hints()
437{
438	my %map = (
439		"python-sphinx"		=> "python3-sphinx",
440		"sphinx_rtd_theme"	=> "python3-sphinx_rtd_theme",
441		"virtualenv"		=> "python3-virtualenv",
442		"dot"			=> "graphviz",
443		"convert"		=> "ImageMagick",
444		"Pod::Usage"		=> "perl-Pod-Usage",
445		"xelatex"		=> "texlive",
446		"rsvg-convert"		=> "librsvg2-tools",
447	);
448
449	my @tex_pkgs = (
450		"texlive-fontsextra",
451	);
452
453	check_rpm_missing(\@tex_pkgs, 1) if ($pdf);
454	check_missing(\%map);
455
456	return if (!$need && !$optional);
457	printf("You should run:\n\n\tsudo urpmi $install\n");
458}
459
460sub give_arch_linux_hints()
461{
462	my %map = (
463		"sphinx_rtd_theme"	=> "python-sphinx_rtd_theme",
464		"virtualenv"		=> "python-virtualenv",
465		"dot"			=> "graphviz",
466		"convert"		=> "imagemagick",
467		"xelatex"		=> "texlive-bin",
468		"rsvg-convert"		=> "extra/librsvg",
469	);
470
471	my @archlinux_tex_pkgs = (
472		"texlive-core",
473		"texlive-latexextra",
474		"ttf-dejavu",
475	);
476	check_pacman_missing(\@archlinux_tex_pkgs, 1) if ($pdf);
477	check_missing(\%map);
478
479	return if (!$need && !$optional);
480	printf("You should run:\n\n\tsudo pacman -S $install\n");
481}
482
483sub give_gentoo_hints()
484{
485	my %map = (
486		"sphinx_rtd_theme"	=> "dev-python/sphinx_rtd_theme",
487		"virtualenv"		=> "dev-python/virtualenv",
488		"dot"			=> "media-gfx/graphviz",
489		"convert"		=> "media-gfx/imagemagick",
490		"xelatex"		=> "dev-texlive/texlive-xetex media-fonts/dejavu",
491		"rsvg-convert"		=> "gnome-base/librsvg",
492	);
493
494	check_missing_file("/usr/share/fonts/dejavu/DejaVuSans.ttf",
495			   "media-fonts/dejavu", 1) if ($pdf);
496
497	check_missing(\%map);
498
499	return if (!$need && !$optional);
500
501	printf("You should run:\n\n");
502	printf("\tsudo su -c 'echo \"media-gfx/imagemagick svg png\" > /etc/portage/package.use/imagemagick'\n");
503	printf("\tsudo su -c 'echo \"media-gfx/graphviz cairo pdf\" > /etc/portage/package.use/graphviz'\n");
504	printf("\tsudo emerge --ask $install\n");
505
506}
507
508sub check_distros()
509{
510	# Distro-specific hints
511	if ($system_release =~ /Red Hat Enterprise Linux/) {
512		give_redhat_hints;
513		return;
514	}
515	if ($system_release =~ /CentOS/) {
516		give_redhat_hints;
517		return;
518	}
519	if ($system_release =~ /Scientific Linux/) {
520		give_redhat_hints;
521		return;
522	}
523	if ($system_release =~ /Oracle Linux Server/) {
524		give_redhat_hints;
525		return;
526	}
527	if ($system_release =~ /Fedora/) {
528		give_redhat_hints;
529		return;
530	}
531	if ($system_release =~ /Ubuntu/) {
532		give_debian_hints;
533		return;
534	}
535	if ($system_release =~ /Debian/) {
536		give_debian_hints;
537		return;
538	}
539	if ($system_release =~ /openSUSE/) {
540		give_opensuse_hints;
541		return;
542	}
543	if ($system_release =~ /Mageia/) {
544		give_mageia_hints;
545		return;
546	}
547	if ($system_release =~ /Arch Linux/) {
548		give_arch_linux_hints;
549		return;
550	}
551	if ($system_release =~ /Gentoo/) {
552		give_gentoo_hints;
553		return;
554	}
555
556	#
557	# Fall-back to generic hint code for other distros
558	# That's far from ideal, specially for LaTeX dependencies.
559	#
560	my %map = (
561		"sphinx-build" => "sphinx"
562	);
563	check_missing_tex(1) if ($pdf);
564	check_missing(\%map);
565	print "I don't know distro $system_release.\n";
566	print "So, I can't provide you a hint with the install procedure.\n";
567	print "There are likely missing dependencies.\n";
568}
569
570#
571# Common dependencies
572#
573
574sub check_needs()
575{
576	# Check for needed programs/tools
577	check_sphinx();
578
579	if ($system_release) {
580		print "Detected OS: $system_release.\n\n";
581	} else {
582		print "Unknown OS\n\n";
583	}
584
585	print "To upgrade Sphinx, use:\n\n" if ($rec_sphinx_upgrade);
586
587	# Check for needed programs/tools
588	check_perl_module("Pod::Usage", 0);
589	check_program("make", 0);
590	check_program("gcc", 0);
591	check_python_module("sphinx_rtd_theme", 1) if (!$virtualenv);
592	check_program("xelatex", 1) if ($pdf);
593	check_program("dot", 1);
594	check_program("convert", 1);
595	check_program("rsvg-convert", 1) if ($pdf);
596	check_program("latexmk", 1) if ($pdf);
597
598	check_distros();
599
600	if ($need_symlink) {
601		printf "\tsudo ln -sf %s /usr/bin/sphinx-build\n\n",
602		       which("sphinx-build-3");
603	}
604	if ($need_sphinx || $rec_sphinx_upgrade) {
605		my $min_activate = "$ENV{'PWD'}/${virtenv_prefix}${min_version}/bin/activate";
606		my @activates = glob "$ENV{'PWD'}/${virtenv_prefix}*/bin/activate";
607
608		@activates = sort {$b cmp $a} @activates;
609
610		if ($need_sphinx && scalar @activates > 0 && $activates[0] ge $min_activate) {
611			printf "\nNeed to activate a compatible Sphinx version on virtualenv with:\n";
612			printf "\t. $activates[0]\n";
613			exit (1);
614		} else {
615			my $rec_activate = "$virtenv_dir/bin/activate";
616			my $virtualenv = findprog("virtualenv-3");
617			$virtualenv = findprog("virtualenv-3.5") if (!$virtualenv);
618			$virtualenv = findprog("virtualenv") if (!$virtualenv);
619			$virtualenv = "virtualenv" if (!$virtualenv);
620
621			printf "\t$virtualenv $virtenv_dir\n";
622			printf "\t. $rec_activate\n";
623			printf "\tpip install -r $requirement_file\n";
624
625			$need++ if (!$rec_sphinx_upgrade);
626		}
627	}
628	printf "\n";
629
630	print "All optional dependencies are met.\n" if (!$optional);
631
632	if ($need == 1) {
633		die "Can't build as $need mandatory dependency is missing";
634	} elsif ($need) {
635		die "Can't build as $need mandatory dependencies are missing";
636	}
637
638	print "Needed package dependencies are met.\n";
639}
640
641#
642# Main
643#
644
645while (@ARGV) {
646	my $arg = shift(@ARGV);
647
648	if ($arg eq "--no-virtualenv") {
649		$virtualenv = 0;
650	} elsif ($arg eq "--no-pdf"){
651		$pdf = 0;
652	} elsif ($arg eq "--version-check"){
653		$version_check = 1;
654	} else {
655		print "Usage:\n\t$0 <--no-virtualenv> <--no-pdf> <--version-check>\n\n";
656		print "Where:\n";
657		print "\t--no-virtualenv\t- Recommend installing Sphinx instead of using a virtualenv\n";
658		print "\t--version-check\t- if version is compatible, don't check for missing dependencies\n";
659		print "\t--no-pdf\t- don't check for dependencies required to build PDF docs\n\n";
660		exit -1;
661	}
662}
663
664#
665# Determine the system type. There's no standard unique way that would
666# work with all distros with a minimal package install. So, several
667# methods are used here.
668#
669# By default, it will use lsb_release function. If not available, it will
670# fail back to reading the known different places where the distro name
671# is stored
672#
673
674$system_release = qx(lsb_release -d) if which("lsb_release");
675$system_release =~ s/Description:\s*// if ($system_release);
676$system_release = catcheck("/etc/system-release") if !$system_release;
677$system_release = catcheck("/etc/redhat-release") if !$system_release;
678$system_release = catcheck("/etc/lsb-release") if !$system_release;
679$system_release = catcheck("/etc/gentoo-release") if !$system_release;
680$system_release = catcheck("/etc/issue") if !$system_release;
681$system_release =~ s/\s+$//;
682
683check_needs;
684