1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
|
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;
}
#-----------------------------------------------------
|