summaryrefslogtreecommitdiff
path: root/dh_installman
diff options
context:
space:
mode:
Diffstat (limited to 'dh_installman')
-rwxr-xr-xdh_installman393
1 files changed, 275 insertions, 118 deletions
diff --git a/dh_installman b/dh_installman
index 858605b2..015d439d 100755
--- a/dh_installman
+++ b/dh_installman
@@ -1,4 +1,4 @@
-#!/usr/bin/perl -w
+#!/usr/bin/perl
=head1 NAME
@@ -7,24 +7,42 @@ dh_installman - install man pages into package build directories
=cut
use strict;
+use warnings;
use File::Find;
use Debian::Debhelper::Dh_Lib;
+our $VERSION = DH_BUILTIN_VERSION;
+
=head1 SYNOPSIS
B<dh_installman> [S<I<debhelper options>>] [S<I<manpage> ...>]
=head1 DESCRIPTION
-B<dh_installman> is a debhelper program that handles installing
-man pages into the correct locations in package build directories. You tell
-it what man pages go in your packages, and it figures out where to install
-them based on the section field in their B<.TH> or B<.Dt> line. If you have
-a properly formatted B<.TH> or B<.Dt> line, your man page will be installed
-into the right directory, with the right name (this includes proper handling
-of pages with a subsection, like B<3perl>, which are placed in F<man3>, and
-given an extension of F<.3perl>). If your B<.TH> or B<.Dt> line is incorrect
-or missing, the program may guess wrong based on the file extension.
+B<dh_installman> is a debhelper program that handles installing man
+pages into the correct locations in package build directories.
+
+In compat 10 and earlier, this program was primarily for when
+upstream's build system does not properly install them as a part of
+its install step (or it does not have an install step). In compat 11
+and later, it also supports the default searchdir plus --sourcedir
+like dh_install(1) and has the advantage that it respects the nodoc
+build profile (unlike dh_install(1)).
+
+Even if you prefer to use L<dh_install(1)> for installing the manpages,
+B<dh_installman> can still be useful for converting the manpage encoding
+to UTF-8 and for converting F<.so> links (as described below). However,
+that part happens automatically without any explicit configuration.
+
+
+You tell B<dh_installman> what man pages go in your packages, and it figures out
+where to install them based on the section field in their B<.TH> or
+B<.Dt> line. If you have a properly formatted B<.TH> or B<.Dt> line,
+your man page will be installed into the right directory, with the
+right name (this includes proper handling of pages with a subsection,
+like B<3perl>, which are placed in F<man3>, and given an extension of
+F<.3perl>). If your B<.TH> or B<.Dt> line is incorrect or missing, the
+program may guess wrong based on the file extension.
It also supports translated man pages, by looking for extensions
like F<.ll.8> and F<.ll_LL.8>, or by use of the B<--language> switch.
@@ -48,6 +66,10 @@ manual page and convert it to UTF-8. If the guesswork fails for some
reason, you can override it using an encoding declaration. See
L<manconv(1)> for details.
+From debhelper compatibility level 11 on, B<dh_install> will fall back to
+looking in F<debian/tmp> for files, if it does not find them in the current
+directory (or wherever you've told it to look using B<--sourcedir>).
+
=head1 FILES
=over 4
@@ -56,6 +78,9 @@ L<manconv(1)> for details.
Lists man pages to be installed.
+Supports substitution variables in compat 13 and later as
+documented in L<debhelper(7)>.
+
=back
=head1 OPTIONS
@@ -72,6 +97,16 @@ acted on.
Use this to specify that the man pages being acted on are written in the
specified language.
+=item B<--sourcedir=>I<dir>
+
+Look in the specified directory for files to be installed. This option
+requires compat 11 or later (it is silently ignored in compat 10 or earlier).
+
+Note that this is not the same as the B<--sourcedirectory> option used
+by the B<dh_auto_>I<*> commands. You rarely need to use this option, since
+B<dh_installman> automatically looks for files in F<debian/tmp> in debhelper
+compatibility level 11 and above.
+
=item I<manpage> ...
Install these man pages into the first package acted on. (Or in all
@@ -79,6 +114,20 @@ packages if B<-A> is specified).
=back
+=head1 EXAMPLES
+
+An example F<debian/manpages> file could look like this:
+
+ doc/man/foo.1
+ # Translations
+ doc/man/foo.da.1
+ doc/man/foo.de.1
+ doc/man/foo.fr.1
+ # NB: The following line is considered a polish translation
+ # of "foo.1" (and not a manpage written in perl called "foo.pl")
+ doc/man/foo.pl.1
+ # ...
+
=head1 NOTES
An older version of this program, L<dh_installmanpages(1)>, is still used
@@ -90,155 +139,229 @@ interface. Use this program instead.
init(options => {
"language=s" => \$dh{LANGUAGE},
+ "sourcedir=s" => \$dh{SOURCEDIR},
});
-my @sofiles;
-my @sodests;
-# PROMISE: DH NOOP WITHOUT manpages tmp(usr/share/man)
+# PROMISE: DH NOOP WITHOUT pkgfile-logged(manpages) tmp(usr/share/man) cli-options()
-foreach my $package (@{$dh{DOPACKAGES}}) {
- next if is_udeb($package);
+my (@sofiles, @sodests);
+my @all_packages = getpackages();
- my $tmp=tmpdir($package);
- my $file=pkgfile($package,"manpages");
- my @manpages;
+my $default_error_handler = compat(10) ? \&glob_expand_error_handler_reject_nomagic_warn_discard : \&glob_expand_error_handler_reject;
+my $nodocs = is_build_profile_active('nodoc') || get_buildoption('nodoc') ? 1 : 0;
+# We cannot assume documentation is built under nodoc, but if it is we must flag it as handled
+# or dh_missing might make noise.
+$default_error_handler = \&glob_expand_error_handler_silently_ignore if $nodocs;
- @manpages=filearray($file, ".") if $file;
+on_items_in_parallel(\@all_packages, sub {
- if (($package eq $dh{FIRSTPACKAGE} || $dh{PARAMS_ALL}) && @ARGV) {
- push @manpages, @ARGV;
- }
+ foreach my $package (@_) {
+ next if is_udeb($package);
- foreach my $page (@manpages) {
- my $basename=basename($page);
+ my $tmp = tmpdir($package);
+ my $file = pkgfile($package, "manpages");
+ my @manpages;
+ my @search_dirs = ('.');
+ my $skip_install = process_pkg($package) ? 0 : 1;
+ my $error_handler = $skip_install ? \&glob_expand_error_handler_silently_ignore : $default_error_handler;
+ @search_dirs = ($dh{SOURCEDIR} // '.', default_sourcedir($package)) if not compat(10);
- # Support compressed pages.
- my $gz='';
- if ($basename=~m/(.*)(\.gz)/) {
- $basename=$1;
- $gz=$2;
- }
+ @manpages = filearray($file, \@search_dirs, $error_handler) if $file;
- my $section;
- # See if there is a .TH or .Dt entry in the man page. If so,
- # we'll pull the section field from that.
- if ($gz) {
- open (IN, "zcat $page|") or die "$page: $!";
- }
- else {
- open (IN, $page) or die "$page: $!";
+ if (($package eq $dh{FIRSTPACKAGE} || $dh{PARAMS_ALL}) && @ARGV) {
+ push @manpages, @ARGV;
}
- while (<IN>) {
- if (/^\.TH\s+\S+\s+"?(\d+[^"\s]*)"?/ ||
- /^\.Dt\s+\S+\s+(\d+[^\s]*)/) {
- $section=$1;
- last;
+
+ log_installed_files($package, @manpages);
+
+ next if $skip_install or $nodocs;
+
+ foreach my $page (@manpages) {
+ my $basename = basename($page);
+
+ # Support compressed pages.
+ my $gz = '';
+ if ($basename =~ m/(.*)(\.gz)/) {
+ $basename = $1;
+ $gz = $2;
}
- }
- # Failing that, we can try to get it from the filename.
- if (! $section) {
- ($section)=$basename=~m/.*\.([1-9]\S*)/;
- }
- # Now get the numeric component of the section.
- my ($realsection)=$section=~m/^(\d)/ if defined $section;
- if (! $realsection) {
- error("Could not determine section for $page");
- }
-
- # Get the man page's name -- everything up to the last dot.
- my ($instname)=$basename=~m/^(.*)\./;
-
- my $destdir="$tmp/usr/share/man/man$realsection/";
- my $langcode;
- if (! defined $dh{LANGUAGE} || ! exists $dh{LANGUAGE}) {
- # Translated man pages are typically specified by adding the
- # language code to the filename, so detect that and
- # redirect to appropriate directory, stripping the code.
- ($langcode)=$basename=~m/.*\.([a-z][a-z](?:_[A-Z][A-Z])?)\.(?:[1-9]|man)/;
- }
- elsif ($dh{LANGUAGE} ne 'C') {
- $langcode=$dh{LANGUAGE};
- }
-
- if (defined $langcode && $langcode ne '') {
- # Strip the language code from the instname.
- $instname=~s/\.$langcode$//;
- }
-
- if (defined $langcode && $langcode ne '') {
- $destdir="$tmp/usr/share/man/$langcode/man$realsection/";
- }
- $destdir=~tr:/:/:s; # just for looks
- my $instpage="$destdir$instname.$section";
+ my ($fd, $section);
+ # See if there is a .TH or .Dt entry in the man page. If so,
+ # we'll pull the section field from that.
+ if ($gz) {
+ $fd = open_gz($page) or error("open $page failed: $!");
+ }
+ else {
+ open($fd, '<', $page) or error("open $page failed: $!");
+ }
+ while (<$fd>) {
+ if (/^\.TH\s+\S+\s+"?(\d+[^"\s]*)"?/ ||
+ /^\.Dt\s+\S+\s+(\d+[^\s]*)/) {
+ $section = $1;
+ if ($section =~ m/^\d+[.]\d+/) {
+ warning("Ignoring section defined in TH/Dt for ${page} as it looks like a version number: ${section}");
+ $section = undef;
+ }
+ last;
+ }
+ }
+ close($fd);
+ # Failing that, we can try to get it from the filename.
+ if (!$section) {
+ ($section) = $basename =~ m/\.([1-9]\w*)$/;
+ }
- next if -l $instpage;
- next if compat(5) && -e $instpage;
+ # Now get the numeric component of the section.
+ my ($realsection) = $section =~ m/^(\d+)/ if defined $section;
+ if (!$realsection or ($realsection < 0 or $realsection > 9)) {
+ warning("Section for ${page} is computed as \"${section}\", which is not a valid section")
+ if defined($section);
+ error("Could not determine section for $page");
+ }
+
+ # Get the man page's name -- everything up to the last dot.
+ my ($instname) = $basename =~ m/^(.*)\./;
+
+ my $destdir = "$tmp/usr/share/man/man$realsection/";
+ my $langcode;
+ if (!defined $dh{LANGUAGE} || !exists $dh{LANGUAGE}) {
+ if (not compat(10) and $page =~ m{/man/(?:([a-z][a-z](?:_[A-Z][A-Z])?)(?:\.[^/]+)?)?/man[1-9]/}) {
+ # If it looks like it was installed in a proper man dir, assume the language
+ # from that is correct.
+ $langcode = $1;
+ } else {
+ # Translated man pages are typically specified by adding the
+ # language code to the filename, so detect that and
+ # redirect to appropriate directory, stripping the code.
+ ($langcode) = $basename =~ m/\.([a-z][a-z](?:_[A-Z][A-Z])?)\.(?:[1-9]|man)/;
+ # Avoid false positives such as /usr/share/man/man8/libnss_myhostname.so.2.8
+ if (defined $langcode && $langcode eq 'so' && $basename =~ /^lib.*\.so(\.[0-9]+)*$/) {
+ $langcode = '';
+ }
+ }
+ } elsif ($dh{LANGUAGE} ne 'C') {
+ $langcode = $dh{LANGUAGE};
+ }
+
+ if (defined $langcode && $langcode ne '') {
+ # Strip the language code from the instname.
+ $instname =~ s/\.$langcode$//;
+ }
+
+ if (defined $langcode && $langcode ne '') {
+ $destdir = "$tmp/usr/share/man/$langcode/man$realsection/";
+ }
+ $destdir =~ tr:/:/:s; # just for looks
+ my $instpage = "$destdir$instname.$section";
+
+ next if -l $instpage;
- if (! -d $destdir) {
- doit "install","-d",$destdir;
+ install_dir($destdir);
+ if ($gz) {
+ doit({ stdout => $instpage }, 'zcat', $page);
+ }
+ else {
+ install_file($page, $instpage);
+ }
}
- if ($gz) {
- complex_doit "zcat \Q$page\E > \Q$instpage\E";
+
+ # Now the .so conversion.
+ @sofiles = @sodests = ();
+ foreach my $dir (qw{usr/share/man}) {
+ if (-e "$tmp/$dir") {
+ find(\&find_so_man, "$tmp/$dir");
+ }
}
- else {
- doit "install","-p","-m644",$page,$instpage;
+ foreach my $sofile (@sofiles) {
+ my $sodest = shift(@sodests);
+ rm_files($sofile);
+ make_symlink_raw_target($sodest, $sofile);
}
}
- # Now the .so conversion.
- @sofiles=@sodests=();
- foreach my $dir (qw{usr/share/man}) {
- if (-e "$tmp/$dir") {
- find(\&find_so_man, "$tmp/$dir");
- }
- }
- foreach my $sofile (@sofiles) {
- my $sodest=shift(@sodests);
- doit "rm","-f",$sofile;
- doit "ln","-sf",$sodest,$sofile;
- }
+});
- # Now utf-8 conversion.
- if (defined `man --version`) {
+# Now utf-8 conversion.
+my $has_man_recode = 0;
+$has_man_recode = 1 if has_man_db_tool('man-recode');
+
+if ($has_man_recode || has_man_db_tool('man')) {
+ my (@manpages_to_reencode, @issues);
+ for my $package (@{$dh{DOPACKAGES}}) {
+ next if is_udeb($package);
+ my $tmp = tmpdir($package);
foreach my $dir (qw{usr/share/man}) {
next unless -e "$tmp/$dir";
- find(sub {
- return if ! -f $_ || -l $_;
- my ($tmp, $orig)=($_.".new", $_);
- complex_doit "man --recode UTF-8 ./\Q$orig\E > \Q$tmp\E";
- # recode uncompresses compressed pages
- doit "rm", "-f", $orig if s/\.(gz|Z)$//;
- doit "chmod", 644, $tmp;
- doit "mv", "-f", $tmp, $_;
- }, "$tmp/$dir");
+ my %seen;
+ my $wanted = sub {
+ my $path = $File::Find::name;
+ return if -l $path || !-f _;
+ if ($path =~ m/\.dh-new$/) {
+ push(@issues, $path);
+ return;
+ }
+ my $uncompressed_name = $path;
+ $uncompressed_name =~ s/\.(?:gz|Z)$//;
+ if (exists($seen{$uncompressed_name})) {
+ my $msg = "Multiple definitions for manpage ${uncompressed_name} via different compressions.";
+ my @values = sort ($path, $seen{$uncompressed_name});
+ my $warn_msg = $msg . ' Picking ' . $values[0] . ' as the canonical definition.';
+ my $error_msg = $msg . ' Please ensure there is at most one definition.';
+ deprecated_functionality($warn_msg, 13, $error_msg);
+ $path = $values[0];
+ warn("Removing conflicting definition of ${uncompressed_name} (" . $values[1]
+ . ') to ensure deterministic behaviour.');
+ rm_files($values[1]);
+ }
+ $seen{$uncompressed_name} = $path;
+ };
+ find({
+ no_chdir => 1,
+ wanted => $wanted,
+ }, "$tmp/$dir");
+ push(@manpages_to_reencode, sort(values(%seen)));
+ }
+
+ if (@issues) {
+ warning("Removing temporary manpages from another dh_installman instance");
+ rm_files(@issues);
+ warning("Possibly race-condition detected or left-overs from an interrupted dh_installman (e.g. with ^C)");
+ error("Please ensure there are no parallel dh_installman's running (for this pkg) and then re-run dh_installman");
}
}
+ if (@manpages_to_reencode) {
+ on_items_in_parallel(\@manpages_to_reencode, \&reencode_manpages);
+ }
+} else {
+ # Should only occur during debhelper building itself (to avoid a B-D on man-db).
+ warning("man is not available. Skipping re-encode of UTF-8 manpages")
}
# Check if a file is a .so man page, for use by File::Find.
sub find_so_man {
- # The -s test is becuase a .so file tends to be small. We don't want
+ # The -s test is because a .so file tends to be small. We don't want
# to open every man page. 1024 is arbitrary.
- if (! -f $_ || -s $_ > 1024 || -s == 0) {
+ if (! -f $_ || -s _ > 1024 || -s _ == 0) {
return;
}
# Test first line of file for the .so thing.
+ my $fd;
if (/\.gz$/) {
- open (SOTEST, "zcat $_|") or die "$_: $!";
+ $fd = open_gz($_) or error("open $_ failed: $!");
}
else {
- open (SOTEST,$_) || die "$_: $!";
+ open($fd, '<', $_) || error("open $_ failed: $!");
}
- my $l=<SOTEST>;
- close SOTEST;
+ my $l = <$fd>;
+ close($fd);
if (! defined $l) {
error("failed to read $_");
}
-
+
if ($l=~m/\.so\s+(.*)\s*/) {
my $solink=$1;
# This test is here to prevent links like ... man8/../man8/foo.8
@@ -252,7 +375,7 @@ sub find_so_man {
elsif ($solink =~ m!/!) {
$solink="../$solink";
}
-
+
if (-e $solink || -e "$solink.gz") {
push @sofiles,"$File::Find::dir/$_";
push @sodests,$solink;
@@ -260,6 +383,40 @@ sub find_so_man {
}
}
+sub has_man_db_tool {
+ my ($tool) = @_;
+ open(my $old_stderr, '>&', *STDERR) or error("dup(STDERR, tmp_fd): $!");
+ # Ignore the error; it is intended as noise-reduction. As long as we can restore
+ # the stderr later, the log will just be slightly more noisy than planned.
+ open(*STDERR, '>', '/dev/null') or warn("redirect stderr to /dev/null failed: $!");
+
+ my $res = defined(`$tool --version`);
+ open(*STDERR, '>&', $old_stderr) or error("dup(tmp_fd, STDERR): $!");
+ close($old_stderr);
+ return $res;
+}
+
+sub reencode_manpages {
+ my (@manpages) = @_;
+ if ($has_man_recode) {
+ xargs(\@manpages, 'man-recode', '--to-code', 'UTF-8', '--suffix', '.dh-new');
+ }
+ for my $manpage (@manpages) {
+ my $manpage_tmp = "${manpage}.dh-new";
+ $manpage_tmp =~ s/\.(?:gz|Z)\.dh-new$/.dh-new/;
+ if (not $has_man_recode) {
+ my $manpage_cmd = ($manpage =~ m{^/}) ? $manpage : "./${manpage}";
+ doit({ stdout => $manpage_tmp }, 'man', '-l', '--recode', 'UTF-8', $manpage_cmd);
+ }
+ # recode uncompresses compressed pages
+ my $orig = $manpage;
+ rm_files($orig) if $manpage =~ s/\.(gz|Z)$//;
+ rename_path($manpage_tmp, $manpage);
+ }
+ # Bulk reset permissions of all re-encoded files
+ xargs(\@manpages, 'chmod', '0644', '--');
+}
+
=head1 SEE ALSO
L<debhelper(7)>