print "============= I am memoize's latexmkrc. John Collins 2024-03-29\n";
# This rc file shows how to use latexmk with the memoize package.
#
# The memoize package (
https://www.ctan.org/pkg/memoize) implements
# externalization and memoization of sections of code in a TeX document.
# It allows the compilation of the document to reuse results of
# compilation-intensive code, with a consequent speeding up of runs of
# *latex. Such compilation intensive code is commonly encountered, for
# example, in making pictures by TikZ.
#
# When a section of code to be memoized is encountered, an extra page is
# inserted in the document's pdf file containing the results of the
# compilation of the section of code. Then a script, memoize-extract.pl or
# memoize-extract.py, is run to extract the extra parts of the the
# document's pdf file to individual pdf files (called externs). On the
# next compilation by *latex, those generated pdf files are used in place
# of actually compiling the corresponding code (unless the section of code
# has changed).
#
# This latexmkrc file supports this process by configuring latexmk so that
# when a compilation by *latex is done, a memoize-extract script is run
# immediately afterwards. If any new externs are generated, latexmk
# notices that and triggers a new compilation, as part of its normal mode
# of operation.
#
# The memoize package itself also runs memoize-extract at the start of each
# compilation. If latexmk has already run memoize-extract, that extra run
# of memoize-extract finds that it has nothing to do. The solution here is
# more general: (a) It handles the case where the memoize package is
# invoked in the .tex file with the external=no option. (b) It handles the
# situation where the aux and out directories are different, which is not
# the case for the built-in invocation. (c) It nicely matches with
# latexmk's general methods of determining how many runs of *latex are
# needed.
#
# Needs latexmk v. >= 4.84, memoize >= 1.2.0 (2024/03/15).
# TeX Live 2024 (or later, presumably).
# Tested on TeX Live 2024 on macOS, Ubuntu, Windows,
# with pdflatex, lualatex, and xelatex.
# Needs perl module PDF::API2 to be installed.
# On TeXLive on Windows, also put
# TEXLIVE_WINDOWS_TRY_EXTERNAL_PERL = 1
# in the texmf.cnf file in the root directory of the TeX Live installation.
# ==> However, there are some anomalies on Windows, which haven't been sorted out
# yet. These notably concern memoize-clean
#
# ==> Fails on MiKTeX on Windows: memoize-extract refuses to create pdf file????
# I haven't yet figured out the problem.
# ==> Also trouble on MiKTeX on macOS: the memoize-extract.pl.exe won't run.
# You can have separate build and output directories, e.g., by
#$out_dir = 'output';
#$aux_dir = 'build';
# or they can be the same, e.g., by
# $out_dir = $aux_dir = 'output';
# Which program and extra options to use for memoize-extract and memoize-clean.
# Note that these are arrays, not simple strings.
our @memoize_extract = ( 'memoize-extract.pl' );
our @memoize_clean = ( 'memoize-clean.pl' );
# Specification of the basic memoize files to delete in a clean-up
# operation. The generated .memo and .pdf files have more specifications,
# and we use memoize-clean to delete those, invoked by a cleanup hook.
push @generated_exts, 'mmz', 'mmz.log';
# Subroutine mmz_analyzes analyzes .mmz file, if it exists **and** was
# generated in current compilation, to determine whether there are new
# extern pdfs to be generated from memo files and pdf file. Communicate
# to subtroutine mmz_extract_new a need to make new externs by setting the
# variable to a non-zero value for $mmz_has_new. Let the value be the
# name of the mmz file; this is perhaps being too elaborate, since the
# standard name of the mmz file can be determined
add_hook( 'after_xlatex_analysis', \&mmz_analyze );
add_hook( 'after_main_pdf', \&mmz_extract_new );
# !!!!!!!!!!!!!!!!!!!! Uncomment the following line **only** if you really
# want latexmk's cleanup operations to delete Memoize's memoization
# files. In a document where these files are time-consuming to make, i.e.,
# in the main use case for the memoize package, the files are precious and
# should only need deleted when that is really needed.
#
# add_hook( 'cleanup', \&mmz_cleanup );
# Whether there are new externs to be made:
my $mmz_has_new = '';
# Scope of this variable: private, from here to end of file.
#-----------------------------------------------------
sub mmz_analyze {
use strict;
print "============= I am mmz_analyze \n";
print " Still to deal with provoking of rerun if directory made\n";
# Analyzes mmz file if generated on this run.
# Then 1. Arranges to trigger making of missing extern pdfs, if needed.
# 2. Sets dependencies on the extern pdfs. This ensures that, in
# the case that one or more extern pdfs does not currently
# exist, a rerun of *latex will triggered after it/they get
# made.
# Return zero on success (or nothing to do), and non-zero otherwise.
# N.B. Current (2024-03-11) hook implementation doesn't use return
# values from hook subroutines. Future implementation might.
# So I'll provide a return value.
my $base = $$Pbase;
my $mmz_file = "$aux_dir1$base.mmz";
# Communicate to subroutine mmz_extract_new about whether new extern
# pdf(s) are to be made. Default to assuming no externs are to be
# made:
$mmz_has_new = '';
if (! -e $mmz_file) {
print "mmz_analyze: No mmz file '$mmz_file', so memoize is not being used.\n";
return 0;
}
# Use (not-currently-documented) latexmk subroutine to detemine
# whether mmz file was generated in current run:
if ( ! test_gen_file_time( $mmz_file) ) {
warn "mmz_analyze: Mmz file '$mmz_file' exists, but wasn't generated\n",
" on this run so memoize is not **currently** being used.\n";
return 0;
}
# Get dependency information.
# We need to cause not-yet-made extern pdfs to be trated as source
# files for *latex.
my $mmz_fh = undef;
if (! open( $mmz_fh, '<', $mmz_file ) ) {
warn "mmz_analyze: Mmz file '$mmz_file' exists, but I can't read it:\n",
" $!\n";
return 1;
}
my @externs = ();
my @dirs = ();
while ( <$mmz_fh> ) {
s/\s*$//; # Remove trailing space, including new lines
if ( /^\\mmzNewExtern\s+{([^}]+)}/ ) {
# We have a new memo item without a corresponding pdf file.
# It will be put in the aux directory.
my $file = "$aux_dir1$1";
print "mmz_analyze: new extern for memoize: '$file'\n";
push @externs, $file;
}
elsif ( /^\\mmzPrefix\s+{([^}]+)}/ ) {
# Prefix.
my $prefix = $1;
if ( $prefix =~ m{^(.*)/[^/]*} ) {
my $dir = $1;
push @dirs, "$aux_dir1$1";
}
}
}
close $mmz_fh;
foreach (@dirs) {
if ( ! -e ) {
my @cmd = ( @memoize_extract, '--mkdir', $_ );
print "mmz_analyze: Making directory '$_' safely by running\n",
" @cmd\n";
mkdir $_;
}
}
rdb_ensure_files_here( @externs );
# .mmz.log is read by Memoize package after it does an internal
# extract, so it appears as an INPUT file in .fls. But it was created
# earlier in the same run by memoize-extract, so it's not a true
# dependency, and should be removed from the list of source files:
rdb_remove_files( $rule, "$mmz_file.log" );
if (@externs ) {
$mmz_has_new = $mmz_file;
}
return 0;
}
#-----------------------------------------------------
sub mmz_extract_new {
use strict;
print "============= I am mmz_extract_new \n";
# If there are new extern pdf files to be made, run memoize-extract to
# make them.
# Situation on entry:
# 1. I'm in the context of a rule for making the document's pdf file.
# When new extern pdf files are to be made, the document's pdf
# file contains the pages to be extracted by memoize-extract.
# Given the rule context, the name of the document's pdf file is
# $$Pdest.
# 2. $mmz_has_new was earlier set to the name of the mmz file with
# the information on the files used for memoization, but only if
# there are new extern pdf(s) to be made.
# 3. If it is empty, then no extern pdfs are to be made. This covers
# the case that the memoize package isn't being used.
# Return zero on success (or nothing to do), and non-zero otherwise.
if ( $mmz_has_new eq '' ) { return 0; }
my $mmz_file = $mmz_has_new;
my ($mmz_file_no_path, $path) = fileparse( $mmz_file );
my $pdf_file = $$Pdest;
# The directory used by memoize-extract for putting the generated
# extern pdfs is passed in the TEXMF_OUTPUT_DIRECTORY environment
# variable. (The need for this way of passing information is
# associated with some security restrictions that apply when
# memoize-extract is called directly from the memoize package in a
# *latex compilation.)
local $ENV{TEXMF_OUTPUT_DIRECTORY} = $aux_dir;
for ('TEXMF_OUTPUT_DIRECTORY') {
print "mmz_extract_new : ENV{$_} = '$ENV{$_}'\n";
}
# So we should give the name of the mmz_file to memoize-extract without
# the directory part.
my @cmd = (@memoize_extract, '--format', 'latex',
'--pdf', $pdf_file, $mmz_file_no_path );
if ( ! -e $pdf_file ) {
warn "mmz_extract_new: Cannot generate externs here, since no pdf file generated\n";
return 1;
}
elsif ( ! test_gen_file($pdf_file) ) {
warn "mmz_extract_new: Pdf file '$pdf_file' exists, but wasn't\n",
" generated on this run. I'll run memoize-extract. Pdf file may contain\n",
" extra pages generated by the memoize package.\n";
return 1;
}
print "make_extract_new: Running\n @cmd\n";
return system @cmd;
}
#-----------------------------------------------------
sub mmz_cleanup {
use strict;
print "============= I am mmz_cleanup \n";
my @cmd = ( @memoize_clean, '--all', '--yes',
'--prefix', $aux_dir,
"$aux_dir1$$Pbase.mmz" );
print "mmz_cleanup: Running\n @cmd\n";
my $ret = system @cmd;
say "Return code $ret";
return $ret;
}
#-----------------------------------------------------