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