Reset the default Verbose value to False.
[scons.git] / src / engine / SCons / Tool / tex.py
1 """SCons.Tool.tex
2
3 Tool-specific initialization for TeX.
4
5 There normally shouldn't be any need to import this module directly.
6 It will usually be imported through the generic SCons.Tool.Tool()
7 selection method.
8
9 """
10
11 #
12 # __COPYRIGHT__
13 #
14 # Permission is hereby granted, free of charge, to any person obtaining
15 # a copy of this software and associated documentation files (the
16 # "Software"), to deal in the Software without restriction, including
17 # without limitation the rights to use, copy, modify, merge, publish,
18 # distribute, sublicense, and/or sell copies of the Software, and to
19 # permit persons to whom the Software is furnished to do so, subject to
20 # the following conditions:
21 #
22 # The above copyright notice and this permission notice shall be included
23 # in all copies or substantial portions of the Software.
24 #
25 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
26 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
27 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 #
33
34 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
35
36 import os.path
37 import re
38 import string
39 import shutil
40
41 import SCons.Action
42 import SCons.Node
43 import SCons.Node.FS
44 import SCons.Util
45 import SCons.Scanner.LaTeX
46
47 Verbose = False
48
49 must_rerun_latex = True
50
51 # these are files that just need to be checked for changes and then rerun latex
52 check_suffixes = ['.toc', '.lof', '.lot', '.out', '.nav', '.snm']
53
54 # these are files that require bibtex or makeindex to be run when they change
55 all_suffixes = check_suffixes + ['.bbl', '.idx', '.nlo', '.glo']
56
57 #
58 # regular expressions used to search for Latex features
59 # or outputs that require rerunning latex
60 #
61 # search for all .aux files opened by latex (recorded in the .log file)
62 openout_aux_re = re.compile(r"\\openout.*`(.*\.aux)'")
63
64 #printindex_re = re.compile(r"^[^%]*\\printindex", re.MULTILINE)
65 #printnomenclature_re = re.compile(r"^[^%]*\\printnomenclature", re.MULTILINE)
66 #printglossary_re = re.compile(r"^[^%]*\\printglossary", re.MULTILINE)
67
68 # search to find rerun warnings
69 warning_rerun_str = '(^LaTeX Warning:.*Rerun)|(^Package \w+ Warning:.*Rerun)'
70 warning_rerun_re = re.compile(warning_rerun_str, re.MULTILINE)
71
72 # search to find citation rerun warnings
73 rerun_citations_str = "^LaTeX Warning:.*\n.*Rerun to get citations correct"
74 rerun_citations_re = re.compile(rerun_citations_str, re.MULTILINE)
75
76 # search to find undefined references or citations warnings
77 undefined_references_str = '(^LaTeX Warning:.*undefined references)|(^Package \w+ Warning:.*undefined citations)'
78 undefined_references_re = re.compile(undefined_references_str, re.MULTILINE)
79
80 # used by the emitter
81 auxfile_re = re.compile(r".", re.MULTILINE)
82 tableofcontents_re = re.compile(r"^[^%\n]*\\tableofcontents", re.MULTILINE)
83 makeindex_re = re.compile(r"^[^%\n]*\\makeindex", re.MULTILINE)
84 bibliography_re = re.compile(r"^[^%\n]*\\bibliography", re.MULTILINE)
85 listoffigures_re = re.compile(r"^[^%\n]*\\listoffigures", re.MULTILINE)
86 listoftables_re = re.compile(r"^[^%\n]*\\listoftables", re.MULTILINE)
87 hyperref_re = re.compile(r"^[^%\n]*\\usepackage.*\{hyperref\}", re.MULTILINE)
88 makenomenclature_re = re.compile(r"^[^%\n]*\\makenomenclature", re.MULTILINE)
89 makeglossary_re = re.compile(r"^[^%\n]*\\makeglossary", re.MULTILINE)
90 beamer_re = re.compile(r"^[^%\n]*\\documentclass\{beamer\}", re.MULTILINE)
91
92 # search to find all files included by Latex
93 include_re = re.compile(r'^[^%\n]*\\(?:include|input){([^}]*)}', re.MULTILINE)
94
95 # search to find all graphics files included by Latex
96 includegraphics_re = re.compile(r'^[^%\n]*\\(?:includegraphics(?:\[[^\]]+\])?){([^}]*)}', re.MULTILINE)
97
98 # search to find all files opened by Latex (recorded in .log file)
99 openout_re = re.compile(r"\\openout.*`(.*)'")
100
101 # list of graphics file extensions for TeX and LaTeX
102 TexGraphics   = SCons.Scanner.LaTeX.TexGraphics
103 LatexGraphics = SCons.Scanner.LaTeX.LatexGraphics
104
105 # An Action sufficient to build any generic tex file.
106 TeXAction = None
107
108 # An action to build a latex file.  This action might be needed more
109 # than once if we are dealing with labels and bibtex.
110 LaTeXAction = None
111
112 # An action to run BibTeX on a file.
113 BibTeXAction = None
114
115 # An action to run MakeIndex on a file.
116 MakeIndexAction = None
117
118 # An action to run MakeIndex (for nomencl) on a file.
119 MakeNclAction = None
120
121 # An action to run MakeIndex (for glossary) on a file.
122 MakeGlossaryAction = None
123
124 # Used as a return value of modify_env_var if the variable is not set.
125 _null = SCons.Scanner.LaTeX._null
126
127 modify_env_var = SCons.Scanner.LaTeX.modify_env_var
128
129 def FindFile(name,suffixes,paths,env):
130     #print "FindFile: name %s, suffixes "% name,suffixes," paths ",paths
131     if Verbose:
132         print " searching for '%s' with extensions: " % name,suffixes
133
134     for path in paths:
135         testName = os.path.join(path,name)
136         if Verbose:
137             print " look for '%s'" % testName
138         if os.path.exists(testName):
139             if Verbose:
140                 print " found '%s'" % testName
141             return env.fs.File(testName)
142         else:
143             name_ext = SCons.Util.splitext(testName)[1]
144             if name_ext:
145                 continue
146
147             # if no suffix try adding those passed in
148             for suffix in suffixes:
149                 testNameExt = testName + suffix
150                 if Verbose:
151                     print " look for '%s'" % testNameExt
152
153                 if os.path.exists(testNameExt):
154                     if Verbose:
155                         print " found '%s'" % testNameExt
156                     return env.fs.File(testNameExt)
157     if Verbose:
158         print " did not find '%s'" % name
159     return None
160
161 def InternalLaTeXAuxAction(XXXLaTeXAction, target = None, source= None, env=None):
162     """A builder for LaTeX files that checks the output in the aux file
163     and decides how many times to use LaTeXAction, and BibTeXAction."""
164
165     global must_rerun_latex
166
167     # This routine is called with two actions. In this file for DVI builds
168     # with LaTeXAction and from the pdflatex.py with PDFLaTeXAction
169     # set this up now for the case where the user requests a different extension
170     # for the target filename
171     if (XXXLaTeXAction == LaTeXAction):
172        callerSuffix = ".dvi"
173     else:
174        callerSuffix = env['PDFSUFFIX']
175
176     basename = SCons.Util.splitext(str(source[0]))[0]
177     basedir = os.path.split(str(source[0]))[0]
178     basefile = os.path.split(str(basename))[1]
179     abspath = os.path.abspath(basedir)
180     targetext = os.path.splitext(str(target[0]))[1]
181     targetdir = os.path.split(str(target[0]))[0]
182
183     saved_env = {}
184     for var in SCons.Scanner.LaTeX.LaTeX.env_variables:
185         saved_env[var] = modify_env_var(env, var, abspath)
186
187     # Create base file names with the target directory since the auxiliary files
188     # will be made there.   That's because the *COM variables have the cd
189     # command in the prolog. We check
190     # for the existence of files before opening them--even ones like the
191     # aux file that TeX always creates--to make it possible to write tests
192     # with stubs that don't necessarily generate all of the same files.
193
194     targetbase = os.path.join(targetdir, basefile)
195
196     # if there is a \makeindex there will be a .idx and thus
197     # we have to run makeindex at least once to keep the build
198     # happy even if there is no index.
199     # Same for glossaries and nomenclature
200     src_content = source[0].get_contents()
201     run_makeindex = makeindex_re.search(src_content) and not os.path.exists(targetbase + '.idx')
202     run_nomenclature = makenomenclature_re.search(src_content) and not os.path.exists(targetbase + '.nlo')
203     run_glossary = makeglossary_re.search(src_content) and not os.path.exists(targetbase + '.glo')
204
205     saved_hashes = {}
206     suffix_nodes = {}
207
208     for suffix in all_suffixes:
209         theNode = env.fs.File(targetbase + suffix)
210         suffix_nodes[suffix] = theNode
211         saved_hashes[suffix] = theNode.get_csig()
212
213     if Verbose:
214         print "hashes: ",saved_hashes
215
216     must_rerun_latex = True
217
218     #
219     # routine to update MD5 hash and compare
220     #
221     def check_MD5(filenode, suffix, saved_hashes=saved_hashes):
222         global must_rerun_latex
223         # two calls to clear old csig
224         filenode.clear_memoized_values()
225         filenode.ninfo = filenode.new_ninfo()
226         new_md5 = filenode.get_csig()
227
228         if saved_hashes[suffix] == new_md5:
229             if Verbose:
230                 print "file %s not changed" % (targetbase+suffix)
231             return False        # unchanged
232         saved_hashes[suffix] = new_md5
233         must_rerun_latex = True
234         if Verbose:
235             print "file %s changed, rerunning Latex, new hash = " % (targetbase+suffix), new_md5
236         return True     # changed
237
238     # generate the file name that latex will generate
239     resultfilename = targetbase + callerSuffix
240
241     count = 0
242
243     while (must_rerun_latex and count < int(env.subst('$LATEXRETRIES'))) :
244         result = XXXLaTeXAction(target, source, env)
245         if result != 0:
246             return result
247
248         count = count + 1
249
250         must_rerun_latex = False
251         # Decide if various things need to be run, or run again.
252
253         # Read the log file to find all .aux files
254         logfilename = targetbase + '.log'
255         logContent = ''
256         auxfiles = []
257         if os.path.exists(logfilename):
258             logContent = open(logfilename, "rb").read()
259             auxfiles = openout_aux_re.findall(logContent)
260
261         # Now decide if bibtex will need to be run.
262         # The information that bibtex reads from the .aux file is
263         # pass-independent. If we find (below) that the .bbl file is unchanged,
264         # then the last latex saw a correct bibliography.
265         # Therefore only do this on the first pass
266         if count == 1:
267             for auxfilename in auxfiles:
268                 target_aux = os.path.join(targetdir, auxfilename)
269                 if os.path.exists(target_aux):
270                     content = open(target_aux, "rb").read()
271                     if string.find(content, "bibdata") != -1:
272                         if Verbose:
273                             print "Need to run bibtex"
274                         bibfile = env.fs.File(targetbase)
275                         result = BibTeXAction(bibfile, bibfile, env)
276                         if result != 0:
277                             return result
278                         must_rerun_latex = check_MD5(suffix_nodes['.bbl'],'.bbl')
279                         break
280
281         # Now decide if latex will need to be run again due to index.
282         if check_MD5(suffix_nodes['.idx'],'.idx') or (count == 1 and run_makeindex):
283             # We must run makeindex
284             if Verbose:
285                 print "Need to run makeindex"
286             idxfile = suffix_nodes['.idx']
287             result = MakeIndexAction(idxfile, idxfile, env)
288             if result != 0:
289                 return result
290
291         # TO-DO: need to add a way for the user to extend this list for whatever
292         # auxiliary files they create in other (or their own) packages
293         # Harder is case is where an action needs to be called -- that should be rare (I hope?)
294
295         for index in check_suffixes:
296             check_MD5(suffix_nodes[index],index)
297
298         # Now decide if latex will need to be run again due to nomenclature.
299         if check_MD5(suffix_nodes['.nlo'],'.nlo') or (count == 1 and run_nomenclature):
300             # We must run makeindex
301             if Verbose:
302                 print "Need to run makeindex for nomenclature"
303             nclfile = suffix_nodes['.nlo']
304             result = MakeNclAction(nclfile, nclfile, env)
305             if result != 0:
306                 return result
307
308         # Now decide if latex will need to be run again due to glossary.
309         if check_MD5(suffix_nodes['.glo'],'.glo') or (count == 1 and run_glossary):
310             # We must run makeindex
311             if Verbose:
312                 print "Need to run makeindex for glossary"
313             glofile = suffix_nodes['.glo']
314             result = MakeGlossaryAction(glofile, glofile, env)
315             if result != 0:
316                 return result
317
318         # Now decide if latex needs to be run yet again to resolve warnings.
319         if warning_rerun_re.search(logContent):
320             must_rerun_latex = True
321             if Verbose:
322                 print "rerun Latex due to latex or package rerun warning"
323
324         if rerun_citations_re.search(logContent):
325             must_rerun_latex = True
326             if Verbose:
327                 print "rerun Latex due to 'Rerun to get citations correct' warning"
328
329         if undefined_references_re.search(logContent):
330             must_rerun_latex = True
331             if Verbose:
332                 print "rerun Latex due to undefined references or citations"
333
334         if (count >= int(env.subst('$LATEXRETRIES')) and must_rerun_latex):
335             print "reached max number of retries on Latex ,",int(env.subst('$LATEXRETRIES'))
336 # end of while loop
337
338     # rename Latex's output to what the target name is
339     if not (str(target[0]) == resultfilename  and  os.path.exists(resultfilename)):
340         if os.path.exists(resultfilename):
341             print "move %s to %s" % (resultfilename, str(target[0]), )
342             shutil.move(resultfilename,str(target[0]))
343
344     # Original comment (when TEXPICTS was not restored):
345     # The TEXPICTS enviroment variable is needed by a dvi -> pdf step
346     # later on Mac OSX so leave it
347     #
348     # It is also used when searching for pictures (implicit dependencies).
349     # Why not set the variable again in the respective builder instead
350     # of leaving local modifications in the environment? What if multiple
351     # latex builds in different directories need different TEXPICTS?
352     for var in SCons.Scanner.LaTeX.LaTeX.env_variables:
353         if var == 'TEXPICTS':
354             continue
355         if saved_env[var] is _null:
356             try:
357                 del env['ENV'][var]
358             except KeyError:
359                 pass # was never set
360         else:
361             env['ENV'][var] = saved_env[var]
362
363     return result
364
365 def LaTeXAuxAction(target = None, source= None, env=None):
366     result = InternalLaTeXAuxAction( LaTeXAction, target, source, env )
367     return result
368
369 LaTeX_re = re.compile("\\\\document(style|class)")
370
371 def is_LaTeX(flist):
372     # Scan a file list to decide if it's TeX- or LaTeX-flavored.
373     for f in flist:
374         content = f.get_contents()
375         if LaTeX_re.search(content):
376             return 1
377     return 0
378
379 def TeXLaTeXFunction(target = None, source= None, env=None):
380     """A builder for TeX and LaTeX that scans the source file to
381     decide the "flavor" of the source and then executes the appropriate
382     program."""
383     if is_LaTeX(source):
384         result = LaTeXAuxAction(target,source,env)
385     else:
386         result = TeXAction(target,source,env)
387     return result
388
389 def TeXLaTeXStrFunction(target = None, source= None, env=None):
390     """A strfunction for TeX and LaTeX that scans the source file to
391     decide the "flavor" of the source and then returns the appropriate
392     command string."""
393     if env.GetOption("no_exec"):
394         if is_LaTeX(source):
395             result = env.subst('$LATEXCOM',0,target,source)+" ..."
396         else:
397             result = env.subst("$TEXCOM",0,target,source)+" ..."
398     else:
399         result = ''
400     return result
401
402 def tex_eps_emitter(target, source, env):
403     """An emitter for TeX and LaTeX sources when
404     executing tex or latex. It will accept .ps and .eps
405     graphics files
406     """
407     (target, source) = tex_emitter_core(target, source, env, TexGraphics)
408
409     return (target, source)
410
411 def tex_pdf_emitter(target, source, env):
412     """An emitter for TeX and LaTeX sources when
413     executing pdftex or pdflatex. It will accept graphics
414     files of types .pdf, .jpg, .png, .gif, and .tif
415     """
416     (target, source) = tex_emitter_core(target, source, env, LatexGraphics)
417
418     return (target, source)
419
420 def tex_emitter_core(target, source, env, graphics_extensions):
421     """An emitter for TeX and LaTeX sources.
422     For LaTeX sources we try and find the common created files that
423     are needed on subsequent runs of latex to finish tables of contents,
424     bibliographies, indices, lists of figures, and hyperlink references.
425     """
426     targetbase = SCons.Util.splitext(str(target[0]))[0]
427     basename = SCons.Util.splitext(str(source[0]))[0]
428     basefile = os.path.split(str(basename))[1]
429
430     basedir = os.path.split(str(source[0]))[0]
431     targetdir = os.path.split(str(target[0]))[0]
432     abspath = os.path.abspath(basedir)
433     target[0].attributes.path = abspath
434
435     #
436     # file names we will make use of in searching the sources and log file
437     #
438     emit_suffixes = ['.aux', '.log', '.ilg', '.blg', '.nls', '.nlg', '.gls', '.glg'] + all_suffixes
439     auxfilename = targetbase + '.aux'
440     logfilename = targetbase + '.log'
441
442     env.SideEffect(auxfilename,target[0])
443     env.SideEffect(logfilename,target[0])
444     env.Clean(target[0],auxfilename)
445     env.Clean(target[0],logfilename)
446
447     content = source[0].get_contents()
448
449     idx_exists = os.path.exists(targetbase + '.idx')
450     nlo_exists = os.path.exists(targetbase + '.nlo')
451     glo_exists = os.path.exists(targetbase + '.glo')
452
453     # set up list with the regular expressions
454     # we use to find features used
455     file_tests_search = [auxfile_re,
456                          makeindex_re,
457                          bibliography_re,
458                          tableofcontents_re,
459                          listoffigures_re,
460                          listoftables_re,
461                          hyperref_re,
462                          makenomenclature_re,
463                          makeglossary_re,
464                          beamer_re ]
465     # set up list with the file suffixes that need emitting
466     # when a feature is found
467     file_tests_suff = [['.aux'],
468                   ['.idx', '.ind', '.ilg'],
469                   ['.bbl', '.blg'],
470                   ['.toc'],
471                   ['.lof'],
472                   ['.lot'],
473                   ['.out'],
474                   ['.nlo', '.nls', '.nlg'],
475                   ['.glo', '.gls', '.glg'],
476                   ['.nav', '.snm', '.out', '.toc'] ]
477     # build the list of lists
478     file_tests = []
479     for i in range(len(file_tests_search)):
480         file_tests.append( [file_tests_search[i].search(content), file_tests_suff[i]] )
481
482     # TO-DO: need to add a way for the user to extend this list for whatever
483     # auxiliary files they create in other (or their own) packages
484
485     inc_files = [str(source[0]), ]
486     inc_files.extend( include_re.findall(content) )
487     if Verbose:
488         print "files included by '%s': "%source[0],inc_files
489     # inc_files is list of file names as given. need to find them
490     # using TEXINPUTS paths.
491
492     # get path list from both env['TEXINPUTS'] and env['ENV']['TEXINPUTS']
493     savedpath = modify_env_var(env, 'TEXINPUTS', abspath)
494     paths = env['ENV']['TEXINPUTS']
495     if SCons.Util.is_List(paths):
496         pass
497     else:
498         # Split at os.pathsep to convert into absolute path
499         paths = paths.split(os.pathsep)
500
501     # now that we have the path list restore the env
502     if savedpath is _null:
503         try:
504             del env['ENV']['TEXINPUTS']
505         except KeyError:
506             pass # was never set
507     else:
508         env['ENV']['TEXINPUTS'] = savedpath
509     if Verbose:
510         print "search path ",paths
511
512     # search all files read (including the original source file)
513     # for files that are \input or \include
514     for src in inc_files:
515         # we already did this for the base file
516         if src != inc_files[0]:
517             content = ""
518             srcNode = FindFile(src,['.tex','.ltx','.latex'],paths,env)
519             if srcNode:
520                 content = srcNode.get_contents()
521             for i in range(len(file_tests_search)):
522                 if file_tests[i][0] == None:
523                     file_tests[i][0] = file_tests_search[i].search(content)
524
525         # For each file see if any graphics files are included
526         # and set up target to create ,pdf graphic
527         # is this is in pdflatex toolchain
528         src_inc_files = includegraphics_re.findall(content)
529         if Verbose:
530             print "graphics files in '%s': "%basename,src_inc_files
531         for graphFile in src_inc_files:
532             graphicNode = FindFile(graphFile,graphics_extensions,paths,env)
533             # see if we can build this graphics file by epstopdf
534             graphicSrc = FindFile(graphFile,TexGraphics,paths,env)
535             if graphicSrc != None:
536                 if Verbose and (graphicNode == None):
537                     print "need to build '%s' by epstopdf %s -o %s" % (graphFile,graphicSrc,graphFile)
538                 graphicNode = env.PDF(os.path.join(targetdir,str(graphicSrc)))
539                 env.Depends(target[0],graphicNode)
540
541     for (theSearch,suffix_list) in file_tests:
542         if theSearch:
543             for suffix in suffix_list:
544                 env.SideEffect(targetbase + suffix,target[0])
545                 env.Clean(target[0],targetbase + suffix)
546
547     # read log file to get all other files that latex creates and will read on the next pass
548     if os.path.exists(logfilename):
549         content = open(logfilename, "rb").read()
550         out_files = openout_re.findall(content)
551         env.SideEffect(out_files,target[0])
552         env.Clean(target[0],out_files)
553
554     return (target, source)
555
556
557 TeXLaTeXAction = None
558
559 def generate(env):
560     """Add Builders and construction variables for TeX to an Environment."""
561
562     # A generic tex file Action, sufficient for all tex files.
563     global TeXAction
564     if TeXAction is None:
565         TeXAction = SCons.Action.Action("$TEXCOM", "$TEXCOMSTR")
566
567     # An Action to build a latex file.  This might be needed more
568     # than once if we are dealing with labels and bibtex.
569     global LaTeXAction
570     if LaTeXAction is None:
571         LaTeXAction = SCons.Action.Action("$LATEXCOM", "$LATEXCOMSTR")
572
573     # Define an action to run BibTeX on a file.
574     global BibTeXAction
575     if BibTeXAction is None:
576         BibTeXAction = SCons.Action.Action("$BIBTEXCOM", "$BIBTEXCOMSTR")
577
578     # Define an action to run MakeIndex on a file.
579     global MakeIndexAction
580     if MakeIndexAction is None:
581         MakeIndexAction = SCons.Action.Action("$MAKEINDEXCOM", "$MAKEINDEXCOMSTR")
582
583     # Define an action to run MakeIndex on a file for nomenclatures.
584     global MakeNclAction
585     if MakeNclAction is None:
586         MakeNclAction = SCons.Action.Action("$MAKENCLCOM", "$MAKENCLCOMSTR")
587
588     # Define an action to run MakeIndex on a file for glossaries.
589     global MakeGlossaryAction
590     if MakeGlossaryAction is None:
591         MakeGlossaryAction = SCons.Action.Action("$MAKEGLOSSARYCOM", "$MAKEGLOSSARYCOMSTR")
592
593     global TeXLaTeXAction
594     if TeXLaTeXAction is None:
595         TeXLaTeXAction = SCons.Action.Action(TeXLaTeXFunction,
596                               strfunction=TeXLaTeXStrFunction)
597
598     import dvi
599     dvi.generate(env)
600
601     bld = env['BUILDERS']['DVI']
602     bld.add_action('.tex', TeXLaTeXAction)
603     bld.add_emitter('.tex', tex_eps_emitter)
604
605     env['TEX']      = 'tex'
606     env['TEXFLAGS'] = SCons.Util.CLVar('-interaction=nonstopmode')
607     env['TEXCOM']   = 'cd ${TARGET.dir} && $TEX $TEXFLAGS ${SOURCE.file}'
608
609     # Duplicate from latex.py.  If latex.py goes away, then this is still OK.
610     env['LATEX']        = 'latex'
611     env['LATEXFLAGS']   = SCons.Util.CLVar('-interaction=nonstopmode')
612     env['LATEXCOM']     = 'cd ${TARGET.dir} && $LATEX $LATEXFLAGS ${SOURCE.file}'
613     env['LATEXRETRIES'] = 3
614
615     env['BIBTEX']      = 'bibtex'
616     env['BIBTEXFLAGS'] = SCons.Util.CLVar('')
617     env['BIBTEXCOM']   = 'cd ${TARGET.dir} && $BIBTEX $BIBTEXFLAGS ${SOURCE.filebase}'
618
619     env['MAKEINDEX']      = 'makeindex'
620     env['MAKEINDEXFLAGS'] = SCons.Util.CLVar('')
621     env['MAKEINDEXCOM']   = 'cd ${TARGET.dir} && $MAKEINDEX $MAKEINDEXFLAGS ${SOURCE.file}'
622
623     env['MAKEGLOSSARY']      = 'makeindex'
624     env['MAKEGLOSSARYSTYLE'] = '${SOURCE.filebase}.ist'
625     env['MAKEGLOSSARYFLAGS'] = SCons.Util.CLVar('-s ${MAKEGLOSSARYSTYLE} -t ${SOURCE.filebase}.glg')
626     env['MAKEGLOSSARYCOM']   = 'cd ${TARGET.dir} && $MAKEGLOSSARY ${SOURCE.filebase}.glo $MAKEGLOSSARYFLAGS -o ${SOURCE.filebase}.gls'
627
628     env['MAKENCL']      = 'makeindex'
629     env['MAKENCLSTYLE'] = '$nomencl.ist'
630     env['MAKENCLFLAGS'] = '-s ${MAKENCLSTYLE} -t ${SOURCE.filebase}.nlg'
631     env['MAKENCLCOM']   = 'cd ${TARGET.dir} && $MAKENCL ${SOURCE.filebase}.nlo $MAKENCLFLAGS -o ${SOURCE.filebase}.nls'
632
633     # Duplicate from pdflatex.py.  If latex.py goes away, then this is still OK.
634     env['PDFLATEX']      = 'pdflatex'
635     env['PDFLATEXFLAGS'] = SCons.Util.CLVar('-interaction=nonstopmode')
636     env['PDFLATEXCOM']   = 'cd ${TARGET.dir} && $PDFLATEX $PDFLATEXFLAGS ${SOURCE.file}'
637
638 def exists(env):
639     return env.Detect('tex')