diff options
| author | Bob Beck <[email protected]> | 2026-08-11 01:47:33 +0200 |
|---|---|---|
| committer | Tomas Mraz <[email protected]> | 2026-09-01 16:02:42 +0200 |
| commit | 49be7ee7839885469abe51428088aab900e76d32 (patch) | |
| tree | e383ea5726cf735cd3e287473cdd81d86de116c4 | |
| parent | Install the HTML manual pages through install-files.pl (diff) | |
| download | openssl-49be7ee7839885469abe51428088aab900e76d32.tar.gz openssl-49be7ee7839885469abe51428088aab900e76d32.zip | |
Stop reloading Pod::Man for every manual page
pod2man is a perl program that compiles Pod::Man before it reads a
single pod, and there are over nine hundred pages, so formatting the
manual spent most of its effort compiling the same module again and
again -- 24 seconds of user time and 6 of system time to produce
about two and a half seconds of formatting.
Format the manual in one invocation that loads the module once and
divides the pages between a few forked workers. No target depends on
an individual page, since the install recipes take them as arguments,
so a stamp is enough to order the work. The per-page rules stay,
calling the same script with a single pod, so one page can still be
built by name, and that path is quicker than pod2man was too.
Reviewed-by: Neil Horman <[email protected]>
Reviewed-by: Andrew Dinh <[email protected]>
Reviewed-by: Tomas Mraz <[email protected]>
Merge-date: Tue Sep 1 14:03:10 2026
Merged-from: https://github.com/openssl/openssl/pull/32243
| -rw-r--r-- | Configurations/unix-Makefile.tmpl | 40 | ||||
| -rw-r--r-- | util/mkpod2man.pl | 131 |
2 files changed, 167 insertions, 4 deletions
diff --git a/Configurations/unix-Makefile.tmpl b/Configurations/unix-Makefile.tmpl index 102ead2513a..bb3ca6498b5 100644 --- a/Configurations/unix-Makefile.tmpl +++ b/Configurations/unix-Makefile.tmpl @@ -291,6 +291,16 @@ MANDOCS7={- fill_lines(" ", $COLUMNS - 9, @{$unified_info{mandocs}->{man7}})) -} +# The pods every manual page is made from, which is what the whole manual +# is formatted from in one go. Each page's pod is the first element of its +# generator, the same place generatesrc() below takes it from. +MANPODS={- + join(" \\\n" . ' ' x 8, + fill_lines(" ", $COLUMNS - 8, + map { $unified_info{generate}->{$_}->[0] } + map { @{$unified_info{mandocs}->{$_} // []} } + qw(man1 man3 man5 man7))) -} + APPS_OPENSSL="{- use File::Spec::Functions; catfile("apps","openssl") -}" @@ -557,7 +567,25 @@ cov-report: $(SHLIBS) ##@ Documentation build_generated_pods: $(GENERATED_PODS) build_docs: build_man_docs build_html_docs ## Create documentation -build_man_docs: $(MANDOCS1) $(MANDOCS3) $(MANDOCS5) $(MANDOCS7) ## Create manpages +build_man_docs: doc/man/.mandocs.stamp ## Create manpages + +# The whole manual is formatted in one run. pod2man is a perl program that +# compiles Pod::Man before it reads a single pod, so a process per page +# spends most of its time loading the same module over nine hundred times. +# No target depends on an individual page -- the install recipes take them +# as arguments -- so a stamp is enough to order the work, and the per-page +# rules below stay available for building one page by name. +doc/man/.mandocs.stamp: {- $config{manpage_format} eq "roff" + ? '$(MANPODS)' + : '$(MANDOCS1) $(MANDOCS3) $(MANDOCS5) $(MANDOCS7)' -} +{- $config{manpage_format} eq "roff" + ? "\t" . '@$(ECHO) "Formatting the manual pages"' + . "\n\t" . '@$(PERL) $(SRCDIR)/util/mkpod2man.pl -o doc/man \\' + . "\n\t\t" . '-m "$(MANSUFFIX)" -d "$(RELEASE_DATE)" -r "$(VERSION)" \\' + . "\n\t\t" . '$(MANPODS)' + . "\n\t" . '@touch $@' + : "\t" . '@touch $@' -} + build_html_docs: $(HTMLDOCS1) $(HTMLDOCS3) $(HTMLDOCS5) $(HTMLDOCS7) ## Create HTML documentation build_generated: $(GENERATED_MANDATORY) @@ -646,6 +674,7 @@ clean: libclean ## Clean the workspace, keep the configuration $(RM) $(MANDOCS3) $(RM) $(MANDOCS5) $(RM) $(MANDOCS7) + $(RM) doc/man/.mandocs.stamp $(RM) $(PROGRAMS) $(TESTPROGS) $(MODULES) $(FIPSMODULE) $(SCRIPTS) $(RM) $(GENERATED_MANDATORY) $(GENERATED) $(RM) core @@ -1577,11 +1606,14 @@ $args{src}: $pod $pod >\$\@ EOF } elsif ($config{manpage_format} eq "roff") { + # One page at a time, through the same script that formats + # the whole manual, so there is a single implementation. It + # does not fork for a single pod, and still beats pod2man, + # which has more of its own start-up to do. return <<"EOF"; $args{src}: $pod - pod2man --name=$name --section=$section\$(MANSUFFIX) --center=OpenSSL \\ - --date=\$(RELEASE_DATE) --release=\$(VERSION) \\ - $pod >\$\@ + \$(PERL) \$(SRCDIR)/util/mkpod2man.pl -o doc/man -m "\$(MANSUFFIX)" \\ + -d "\$(RELEASE_DATE)" -r "\$(VERSION)" $pod EOF } else { die "Unhandled manpage format: $config{manpage_format}"; diff --git a/util/mkpod2man.pl b/util/mkpod2man.pl new file mode 100644 index 00000000000..08903e47559 --- /dev/null +++ b/util/mkpod2man.pl @@ -0,0 +1,131 @@ +#! /usr/bin/env perl +# Copyright 2026 The OpenSSL Project Authors. All Rights Reserved. +# +# Licensed under the Apache License 2.0 (the "License"). You may not use +# this file except in compliance with the License. You can obtain a copy +# in the file LICENSE in the source distribution or at +# https://www.openssl.org/source/license.html + +use strict; +use warnings; + +use Getopt::Std; +use File::Basename; +use Pod::Man; + +# Format man pages without loading Pod::Man once per page. pod2man is a +# perl program that compiles Pod::Man before it reads a single pod, and +# OpenSSL has over nine hundred pages, so running it once per page spends +# most of that time compiling the same module again and again. Here the +# module is loaded once and the pages are then divided between a few +# forked workers, which inherit it already compiled. + +# Options. The whole manual is formatted in one run, so the section of +# each page is taken from the directory its pod lives in rather than +# given on the command line: doc/man3/BIO_s_mem.pod is section 3 and +# becomes OUTDIR/man3/BIO_s_mem.3. +our ($opt_o); # -o OUTDIR, the directory holding the man1..man7 dirs +our ($opt_m); # -m MANSUFFIX, appended to the section inside the page +our ($opt_d); # -d DATE +our ($opt_r); # -r RELEASE +our ($opt_j); # -j JOBS + +getopts('o:m:d:r:j:'); +die "-o flag missing" unless defined $opt_o; +$opt_m = '' unless defined $opt_m; +$opt_d = '' unless defined $opt_d; +$opt_r = '' unless defined $opt_r; + +sub cpu_count +{ + my $cpus = $ENV{"NUMBER_OF_PROCESSORS"}; # Windows sets this. + + if (!defined($cpus) && $^O =~ /linux/) { + my $tmp = qx(nproc 2>/dev/null); + + $cpus = $tmp if $? == 0 && $tmp > 0; + } + if (!defined($cpus) && -r "/proc/cpuinfo") { + my $tmp = qx(grep -c ^processor /proc/cpuinfo 2>/dev/null); + + $cpus = $tmp if $? == 0 && $tmp > 0; + } + if (!defined($cpus)) { + my $tmp = qx(sysctl -n hw.ncpu 2>/dev/null); # BSDs, macOS + + $cpus = $tmp if $? == 0 && $tmp > 0; + } + + return defined($cpus) && $cpus > 0 ? int($cpus) : 1; +} + +# Turn one pod into one man page. +sub format_page +{ + my $pod = shift; + my $name = basename($pod, ".pod"); + my $dir = basename(dirname($pod)); + my ($section) = $dir =~ m|^man(\d)$|; + + die "Can't tell the section of $pod from its directory\n" + unless defined $section; + + my $out = "$opt_o/man$section/$name.$section"; + + # The page is current when it is newer than the pod it comes from. + return if -e $out && -M $out < -M $pod; + + Pod::Man->new(name => uc $name, + section => "$section$opt_m", + center => "OpenSSL", + date => $opt_d, + release => $opt_r) + ->parse_from_file($pod, $out); +} + +my @pods = @ARGV; + +exit 0 unless @pods; + +# Only ask how many processors there are when the answer can matter; +# the count is found by running a command, and the per-page rules call +# this script with a single pod. +my $jobs = @pods > 1 + ? (defined $opt_j && $opt_j > 0 ? int($opt_j) : cpu_count()) + : 1; + +$jobs = scalar @pods if $jobs > @pods; + +# One page, or no reason to fork: do the work here. This is also the path +# taken where fork() is emulated and would cost more than it saves. +if ($jobs <= 1) { + format_page($_) foreach @pods; + exit 0; +} + +my @pids; + +foreach my $worker (0 .. $jobs - 1) { + my $pid = fork(); + + die "Can't fork, $!\n" unless defined $pid; + if (!$pid) { + # Deal every $jobs'th page to this worker. The pages differ a lot + # in size, and dealing them out interleaves the large ones instead + # of handing one worker a contiguous run of them. + for (my $i = $worker; $i <= $#pods; $i += $jobs) { + format_page($pods[$i]); + } + exit 0; + } + push @pids, $pid; +} + +my $failed = 0; + +foreach my $pid (@pids) { + waitpid($pid, 0); + $failed = 1 if $?; +} + +die "Failed to format the manual pages\n" if $failed; |
