Commit 2c4b71ecb0 for openssl.org
commit 2c4b71ecb026fc7605c213db1854706d822e30d0
Author: Nikola Pajkovsky <nikolap@openssl.org>
Date: Wed Oct 14 22:03:44 2026 +0200
Pass the pod lists for HTML/man generation in a file
The man and HTML stamps handed the whole manual to mkpod2man.pl
and mkpod2html.pl on the command line, so every build had the shell
expand over a hundred paths into one command.
Write the lists to doc/man/.mandocs.files and doc/html/.htmldocs.files
while the build file is generated. The writing is shared by a new
template sub, write_list_file(), which only replaces a file
when its list changes, renames it into place and leaves it read-only.
The stamps now depend only on the list files and pass them with a new
-f option. mkpod2man.pl and mkpod2html.pl still accept pods
as arguments, so the per-page rules are unchanged.
Both lists come from the new OpenSSL::Config::Query methods
get_manpods() and get_htmlpods(). Make rules use those methods through
configdata.pm --query to recreate a list that is missing, or whose pods
have been edited. The lists depend on their pods, so an edited pod
refreshes the list and that, in turn, redoes the stamp. They don't
depend on configdata.pm, since an unchanged list is left older than it,
and remaking it would redo the whole set for nothing. The list never
passes through the shell. Both scripts now also create the manN output
directory, so the documentation can be rebuilt after doc/man or doc/html
has been removed. distclean removes the lists.
Resolves: https://github.com/openssl/openssl/issues/33060
Fixes: ad3d4e465658 "Stop reloading Pod::Html for every manual page"
Assisted-by: Pi:DeepSeek-V4.1-Flash-1017
Signed-off-by: Nikola Pajkovsky <nikolap@openssl.org>
Reviewed-by: Richard Levitte <levitte@openssl.foundation>
Reviewed-by: Andrew Dinh <andrewd@openssl.org>
Reviewed-by: Eugene Syromiatnikov <esyr@openssl.org>
Merge-date: Tue Nov 10 06:40:48 2026
Merged-from: https://github.com/openssl/openssl/pull/33218
diff --git a/Configurations/unix-Makefile.tmpl b/Configurations/unix-Makefile.tmpl
index e2f4d54e4c..686f340309 100644
--- a/Configurations/unix-Makefile.tmpl
+++ b/Configurations/unix-Makefile.tmpl
@@ -4,8 +4,11 @@
## {- join("\n## ", @autowarntext) -}
{-
use Time::Piece;
+ use File::Basename;
+ use File::Path qw(mkpath);
use OpenSSL::Util;
+ use OpenSSL::Config::Query;
our $makedep_scheme = $config{makedep_scheme};
our $makedepcmd = platform->makedepcmd();
@@ -66,6 +69,35 @@
}
return @result;
}
+
+ # Write a list, one item per line, to a file while the build file is
+ # generated. The doc stamps hand such a file to mkpod2man.pl and
+ # mkpod2html.pl instead of having the shell expand nine hundred paths
+ # onto a single command line. The file is only rewritten when the list
+ # changes, and is left read-only like the other generated files.
+ sub write_list_file {
+ my $file = shift;
+ my $list = join('', map { "$_\n" } @_);
+ my $old;
+
+ if (open my $fh, '<', $file) {
+ local $/;
+ $old = <$fh>;
+ close $fh;
+ }
+ return if defined $old && $old eq $list;
+
+ mkpath(dirname($file));
+ open my $fh, '>', "$file.new"
+ or die "Trying to create $file.new: $!";
+ print $fh $list;
+ close $fh
+ or die "Trying to write $file.new: $!";
+ chmod 0444, "$file.new"
+ or die "Trying to change mode of $file.new: $!";
+ rename "$file.new", $file
+ or die "Trying to rename $file.new to $file: $!";
+ }
'';
-}
PLATFORM={- $config{target} -}
@@ -276,14 +308,16 @@ HTMLDOCS7={-
@{$unified_info{htmldocs}->{man7}})) -}
# The pods every HTML page is made from, which is what the whole set is
-# converted from in one go. Each page's pod is the first element of its
-# generator, the same place generatesrc() below takes it from.
+# converted from in one go. The list is also written to
+# doc/html/.htmldocs.files for the stamp's recipe, see write_list_file().
HTMLPODS={-
+ my @htmlpods =
+ @{OpenSSL::Config::Query->new(info => \%unified_info)
+ ->get_htmlpods()};
+
+ write_list_file('doc/html/.htmldocs.files', @htmlpods);
join(" \\\n" . ' ' x 9,
- fill_lines(" ", $COLUMNS - 9,
- map { $unified_info{generate}->{$_}->[0] }
- map { @{$unified_info{htmldocs}->{$_} // []} }
- qw(man1 man3 man5 man7))) -}
+ fill_lines(" ", $COLUMNS - 9, @htmlpods)) -}
MANDOCS1={-
join(" \\\n" . ' ' x 9,
@@ -303,14 +337,16 @@ MANDOCS7={-
@{$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.
+# is formatted from in one go. The list is also written to
+# doc/man/.mandocs.files for the stamp's recipe, see write_list_file().
MANPODS={-
+ my @manpods =
+ @{OpenSSL::Config::Query->new(info => \%unified_info)
+ ->get_manpods()};
+
+ write_list_file('doc/man/.mandocs.files', @manpods);
join(" \\\n" . ' ' x 8,
- fill_lines(" ", $COLUMNS - 8,
- map { $unified_info{generate}->{$_}->[0] }
- map { @{$unified_info{mandocs}->{$_} // []} }
- qw(man1 man3 man5 man7))) -}
+ fill_lines(" ", $COLUMNS - 8, @manpods)) -}
APPS_OPENSSL="{- use File::Spec::Functions;
catfile("apps","openssl") -}"
@@ -596,16 +632,31 @@ build_man_docs: doc/man/.mandocs.stamp ## Create manpages
# 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)'
+ ? 'doc/man/.mandocs.files'
: '$(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\t" . '-f doc/man/.mandocs.files'
. "\n\t" . '@touch $@'
: "\t" . '@touch $@' -}
+# The pod lists are written when the build file is generated, see
+# write_list_file(), which is also what keeps their contents current: the
+# list can only change when the configuration does, and that regenerates
+# this file. These rules make a missing list again, still without the list
+# ever reaching a command line, and remake it when one of its pods is
+# edited. That refreshes the list's timestamp, which is how the stamp,
+# depending only on the list, learns that a pod changed. They deliberately
+# don't depend on configdata.pm, write_list_file() leaves an unchanged list
+# older than it, and refreshing the list would make the stamps redo the
+# whole set for nothing.
+doc/man/.mandocs.files: $(MANPODS)
+ @$(PERL) $(SRCDIR)/util/mkdir-p.pl doc/man
+ @$(PERL) $(SRCDIR)/util/file-from-stdout.pl $@ \
+ $(PERL) configdata.pm --query 'get_manpods()'
+
build_html_docs: doc/html/.htmldocs.stamp ## Create HTML documentation
# As with the manual pages, Pod::Html is compiled before the first pod is
@@ -613,12 +664,17 @@ build_html_docs: doc/html/.htmldocs.stamp ## Create HTML documentation
# process per page. Nothing depends on an individual page -- the install
# recipe takes them as arguments -- so a stamp is enough to order the work,
# and the per-page rules below stay available for one page by name.
-doc/html/.htmldocs.stamp: $(HTMLPODS)
+doc/html/.htmldocs.stamp: doc/html/.htmldocs.files
@$(ECHO) "Converting the manual pages to HTML"
@$(PERL) $(SRCDIR)/util/mkpod2html.pl -o doc/html \
- -r "$(SRCDIR)/doc" $(HTMLPODS)
+ -r "$(SRCDIR)/doc" -f doc/html/.htmldocs.files
@touch $@
+doc/html/.htmldocs.files: $(HTMLPODS)
+ @$(PERL) $(SRCDIR)/util/mkdir-p.pl doc/html
+ @$(PERL) $(SRCDIR)/util/file-from-stdout.pl $@ \
+ $(PERL) configdata.pm --query 'get_htmlpods()'
+
build_generated: $(GENERATED_MANDATORY)
build_libs_nodep: $(LIBS) {- join(" ",map { platform->sharedlib_simple($_) // platform->sharedlib_import($_) // platform->sharedlib($_) // () } @{$unified_info{libraries}}) -}
build_modules_nodep: $(MODULES)
@@ -736,6 +792,8 @@ clean: libclean ## Clean the workspace, keep the configuration
distclean: clean cov-clean ## Clean and remove the configuration
$(RM) include/openssl/configuration.h
+ $(RM) doc/man/.mandocs.files
+ $(RM) doc/html/.htmldocs.files
$(RM) configdata.pm
$(RM) Makefile
diff --git a/util/mkpod2html.pl b/util/mkpod2html.pl
index d23ee4b2ea..1e887ddcd1 100755
--- a/util/mkpod2html.pl
+++ b/util/mkpod2html.pl
@@ -12,6 +12,7 @@ use warnings;
use lib ".";
use Getopt::Std;
use File::Basename;
+use File::Path qw(mkpath);
use Pod::Html;
use File::Spec::Functions qw(:DEFAULT rel2abs);
@@ -24,12 +25,14 @@ use File::Spec::Functions qw(:DEFAULT rel2abs);
# Options.
our ($opt_i); # -i INFILE, one page; without it, pods are read from @ARGV
+ # and the -f file
our ($opt_o); # -o OUTFILE with -i, otherwise the directory to write into
our ($opt_t); # -t TITLE, only with -i
our ($opt_r); # -r PODROOT
our ($opt_j); # -j JOBS, only without -i
+our ($opt_f); # -f FILE, a file naming more pods, one per line, only without -i
-getopts('i:o:t:r:j:');
+getopts('i:o:t:r:j:f:');
die "-o flag missing" unless $opt_o;
die "-r flag missing" unless $opt_r;
@@ -71,6 +74,7 @@ sub format_page
$out = rel2abs($out) or die "Can't convert to real path: $!";
unlink $out;
+ mkpath(dirname($out));
pod2html "--infile=$pod",
"--outfile=$out",
@@ -119,8 +123,19 @@ sub page_of
return ("$opt_o/man$section/$name.html", $name);
}
+# The pods come from the command line and from the file given with -f,
+# which is how the whole set is named without the shell expanding every
+# path onto one command line.
my @pods = @ARGV;
+if (defined $opt_f) {
+ open my $fh, '<', $opt_f
+ or die "Can't read $opt_f, $!\n";
+ chomp(my @listed = <$fh>);
+ close $fh;
+ push @pods, grep { $_ ne '' } @listed;
+}
+
exit 0 unless @pods;
# Only ask how many processors there are when the answer can matter; the
diff --git a/util/mkpod2man.pl b/util/mkpod2man.pl
index 778dc2b091..2d366949cc 100644
--- a/util/mkpod2man.pl
+++ b/util/mkpod2man.pl
@@ -11,6 +11,7 @@ use warnings;
use Getopt::Std;
use File::Basename;
+use File::Path qw(mkpath);
use Pod::Man;
# Format man pages without loading Pod::Man once per page. pod2man is a
@@ -29,8 +30,9 @@ 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
+our ($opt_f); # -f FILE, a file naming the pods, one per line
-getopts('o:m:d:r:j:');
+getopts('o:m:d:r:j:f:');
die "-o flag missing" unless defined $opt_o;
$opt_m = '' unless defined $opt_m;
$opt_d = '' unless defined $opt_d;
@@ -76,6 +78,7 @@ sub format_page
return if -e $out && -M $out < -M $pod;
unlink $out;
+ mkpath(dirname($out));
Pod::Man->new(name => uc $name,
section => "$section$opt_m",
@@ -88,8 +91,20 @@ sub format_page
or die "Can't set the mode of $out, $!\n";
}
+# The pods come from the command line, which is how the per-page rules
+# name their one pod, and from the file given with -f, which is how the
+# whole manual is named without the shell expanding every path onto one
+# command line.
my @pods = @ARGV;
+if (defined $opt_f) {
+ open my $fh, '<', $opt_f
+ or die "Can't read $opt_f, $!\n";
+ chomp(my @listed = <$fh>);
+ close $fh;
+ push @pods, grep { $_ ne '' } @listed;
+}
+
exit 0 unless @pods;
# Only ask how many processors there are when the answer can matter;
diff --git a/util/perl/OpenSSL/Config/Query.pm b/util/perl/OpenSSL/Config/Query.pm
index 22d6a459bd..b3fbb30c7f 100644
--- a/util/perl/OpenSSL/Config/Query.pm
+++ b/util/perl/OpenSSL/Config/Query.pm
@@ -150,6 +150,37 @@ sub get_config {
return { map { $_ => $self->{config}->{$_} } @_ };
}
+=item B<get_manpods>
+
+=item B<get_htmlpods>
+
+The returned result is an array reference holding the pod every manual page,
+or every HTML page, is made from, section by section. Each page's pod is the
+first element of its generator.
+
+=cut
+
+sub get_manpods {
+ my $self = shift;
+
+ return $self->_get_doc_pods('mandocs');
+}
+
+sub get_htmlpods {
+ my $self = shift;
+
+ return $self->_get_doc_pods('htmldocs');
+}
+
+sub _get_doc_pods {
+ my $self = shift;
+ my $kind = shift; # 'mandocs' or 'htmldocs'
+
+ return [ map { $self->{info}->{generate}->{$_}->[0] }
+ map { @{$self->{info}->{$kind}->{$_} // []} }
+ qw(man1 man3 man5 man7) ];
+}
+
########
#
# Helper functions