c05ffc80d9b826548bffff8aa3216b2e05037177
[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) and os.path.isfile(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) and os.path.isfile(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) and os.path.isfile(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) and os.path.isfile(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) and os.path.isfile(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  
379             os.path.exists(resultfilename) and 
380             os.path.isfile(resultfilename)):
381         if os.path.exists(resultfilename) and os.path.isfile(resultfilename):
382             print "move %s to %s" % (resultfilename, str(target[0]), )
383             shutil.move(resultfilename,str(target[0]))
384
385     # Original comment (when TEXPICTS was not restored):
386     # The TEXPICTS enviroment variable is needed by a dvi -> pdf step
387     # later on Mac OSX so leave it
388     #
389     # It is also used when searching for pictures (implicit dependencies).
390     # Why not set the variable again in the respective builder instead
391     # of leaving local modifications in the environment? What if multiple
392     # latex builds in different directories need different TEXPICTS?
393     for var in SCons.Scanner.LaTeX.LaTeX.env_variables:
394         if var == 'TEXPICTS':
395             continue
396         if saved_env[var] is _null:
397             try:
398                 del env['ENV'][var]
399             except KeyError:
400                 pass # was never set
401         else:
402             env['ENV'][var] = saved_env[var]
403
404     return result
405
406 def LaTeXAuxAction(target = None, source= None, env=None):
407     result = InternalLaTeXAuxAction( LaTeXAction, target, source, env )
408     return result
409
410 LaTeX_re = re.compile("\\\\document(style|class)")
411
412 def is_LaTeX(flist,env,abspath):
413     """Scan a file list to decide if it's TeX- or LaTeX-flavored."""
414
415     # We need to scan files that are included in case the
416     # \documentclass command is in them.
417
418     # get path list from both env['TEXINPUTS'] and env['ENV']['TEXINPUTS']
419     savedpath = modify_env_var(env, 'TEXINPUTS', abspath)
420     paths = env['ENV']['TEXINPUTS']
421     if SCons.Util.is_List(paths):
422         pass
423     else:
424         # Split at os.pathsep to convert into absolute path
425         # TODO(1.5)
426         #paths = paths.split(os.pathsep)
427         paths = string.split(paths, os.pathsep)
428
429     # now that we have the path list restore the env
430     if savedpath is _null:
431         try:
432             del env['ENV']['TEXINPUTS']
433         except KeyError:
434             pass # was never set
435     else:
436         env['ENV']['TEXINPUTS'] = savedpath
437     if Verbose:
438         print "is_LaTeX search path ",paths
439         print "files to search :",flist
440
441     # Now that we have the search path and file list, check each one
442     for f in flist:
443         if Verbose:
444             print " checking for Latex source ",str(f)
445
446         content = f.get_text_contents()
447         if LaTeX_re.search(content):
448             if Verbose:
449                 print "file %s is a LaTeX file" % str(f)
450             return 1
451         if Verbose:
452             print "file %s is not a LaTeX file" % str(f)
453
454         # now find included files
455         inc_files = [ ]
456         inc_files.extend( include_re.findall(content) )
457         if Verbose:
458             print "files included by '%s': "%str(f),inc_files
459         # inc_files is list of file names as given. need to find them
460         # using TEXINPUTS paths.
461
462         # search the included files
463         for src in inc_files:
464             srcNode = FindFile(src,['.tex','.ltx','.latex'],paths,env,requireExt=False)
465             # make this a list since is_LaTeX takes a list.
466             fileList = [srcNode,]
467             if Verbose:
468                 print "FindFile found ",srcNode
469             if srcNode is not None:
470                 file_test = is_LaTeX(fileList, env, abspath)
471
472             # return on first file that finds latex is needed.
473             if file_test:
474                 return file_test
475
476         if Verbose:
477             print " done scanning ",str(f)
478
479     return 0
480
481 def TeXLaTeXFunction(target = None, source= None, env=None):
482     """A builder for TeX and LaTeX that scans the source file to
483     decide the "flavor" of the source and then executes the appropriate
484     program."""
485
486     # find these paths for use in is_LaTeX to search for included files
487     basedir = os.path.split(str(source[0]))[0]
488     abspath = os.path.abspath(basedir)
489
490     if is_LaTeX(source,env,abspath):
491         result = LaTeXAuxAction(target,source,env)
492         if result != 0:
493             print env['LATEX']," returned an error, check the log file"
494     else:
495         result = TeXAction(target,source,env)
496         if result != 0:
497             print env['TEX']," returned an error, check the log file"
498     return result
499
500 def TeXLaTeXStrFunction(target = None, source= None, env=None):
501     """A strfunction for TeX and LaTeX that scans the source file to
502     decide the "flavor" of the source and then returns the appropriate
503     command string."""
504     if env.GetOption("no_exec"):
505
506         # find these paths for use in is_LaTeX to search for included files
507         basedir = os.path.split(str(source[0]))[0]
508         abspath = os.path.abspath(basedir)
509
510         if is_LaTeX(source,env,abspath):
511             result = env.subst('$LATEXCOM',0,target,source)+" ..."
512         else:
513             result = env.subst("$TEXCOM",0,target,source)+" ..."
514     else:
515         result = ''
516     return result
517
518 def tex_eps_emitter(target, source, env):
519     """An emitter for TeX and LaTeX sources when
520     executing tex or latex. It will accept .ps and .eps
521     graphics files
522     """
523     (target, source) = tex_emitter_core(target, source, env, TexGraphics)
524
525     return (target, source)
526
527 def tex_pdf_emitter(target, source, env):
528     """An emitter for TeX and LaTeX sources when
529     executing pdftex or pdflatex. It will accept graphics
530     files of types .pdf, .jpg, .png, .gif, and .tif
531     """
532     (target, source) = tex_emitter_core(target, source, env, LatexGraphics)
533
534     return (target, source)
535
536 def ScanFiles(theFile, target, paths, file_tests, file_tests_search, env, graphics_extensions, targetdir, aux_files):
537     """ For theFile (a Node) update any file_tests and search for graphics files
538     then find all included files and call ScanFiles recursively for each of them"""
539
540     content = theFile.get_text_contents()
541     if Verbose:
542         print " scanning ",str(theFile)
543
544     for i in range(len(file_tests_search)):
545         if file_tests[i][0] is None:
546             file_tests[i][0] = file_tests_search[i].search(content)
547
548     incResult = includeOnly_re.search(content)
549     if incResult:
550         aux_files.append(os.path.join(targetdir, incResult.group(1)))
551     if Verbose:
552         print "\include file names : ", aux_files
553     # recursively call this on each of the included files
554     inc_files = [ ]
555     inc_files.extend( include_re.findall(content) )
556     if Verbose:
557         print "files included by '%s': "%str(theFile),inc_files
558     # inc_files is list of file names as given. need to find them
559     # using TEXINPUTS paths.
560
561     for src in inc_files:
562         srcNode = FindFile(src,['.tex','.ltx','.latex'],paths,env,requireExt=False)
563         if srcNode is not None:
564             file_tests = ScanFiles(srcNode, target, paths, file_tests, file_tests_search, env, graphics_extensions, targetdir, aux_files)
565     if Verbose:
566         print " done scanning ",str(theFile)
567     return file_tests
568
569 def tex_emitter_core(target, source, env, graphics_extensions):
570     """An emitter for TeX and LaTeX sources.
571     For LaTeX sources we try and find the common created files that
572     are needed on subsequent runs of latex to finish tables of contents,
573     bibliographies, indices, lists of figures, and hyperlink references.
574     """
575     basename = SCons.Util.splitext(str(source[0]))[0]
576     basefile = os.path.split(str(basename))[1]
577     targetdir = os.path.split(str(target[0]))[0]
578     targetbase = os.path.join(targetdir, basefile)
579
580     basedir = os.path.split(str(source[0]))[0]
581     abspath = os.path.abspath(basedir)
582     target[0].attributes.path = abspath
583     
584     #
585     # file names we will make use of in searching the sources and log file
586     #
587     emit_suffixes = ['.aux', '.log', '.ilg', '.blg', '.nls', '.nlg', '.gls', '.glg', '.alg'] + all_suffixes
588     auxfilename = targetbase + '.aux'
589     logfilename = targetbase + '.log'
590     flsfilename = targetbase + '.fls'
591
592     env.SideEffect(auxfilename,target[0])
593     env.SideEffect(logfilename,target[0])
594     env.SideEffect(flsfilename,target[0])
595     if Verbose:
596         print "side effect :",auxfilename,logfilename,flsfilename
597     env.Clean(target[0],auxfilename)
598     env.Clean(target[0],logfilename)
599     env.Clean(target[0],flsfilename)
600
601     content = source[0].get_text_contents()
602
603     # not sure what these were for but they are unused
604     #idx_exists = os.path.exists(targetbase + '.idx') and os.path.isfile(targetbase + '.idx')
605     #nlo_exists = os.path.exists(targetbase + '.nlo') and os.path.isfile(targetbase + '.nlo')
606     #glo_exists = os.path.exists(targetbase + '.glo') and os.path.isfile(targetbase + '.glo')
607     #acr_exists = os.path.exists(targetbase + '.acn') and os.path.isfile(targetbase + '.acn')
608
609     # set up list with the regular expressions
610     # we use to find features used
611     file_tests_search = [auxfile_re,
612                          makeindex_re,
613                          bibliography_re,
614                          tableofcontents_re,
615                          listoffigures_re,
616                          listoftables_re,
617                          hyperref_re,
618                          makenomenclature_re,
619                          makeglossary_re,
620                          makeglossaries_re,
621                          makeacronyms_re,
622                          beamer_re ]
623     # set up list with the file suffixes that need emitting
624     # when a feature is found
625     file_tests_suff = [['.aux'],
626                   ['.idx', '.ind', '.ilg'],
627                   ['.bbl', '.blg'],
628                   ['.toc'],
629                   ['.lof'],
630                   ['.lot'],
631                   ['.out'],
632                   ['.nlo', '.nls', '.nlg'],
633                   ['.glo', '.gls', '.glg'],
634                   ['.glo', '.gls', '.glg'],
635                   ['.acn', '.acr', '.alg'],
636                   ['.nav', '.snm', '.out', '.toc'] ]
637     # build the list of lists
638     file_tests = []
639     for i in range(len(file_tests_search)):
640         file_tests.append( [None, file_tests_suff[i]] )
641
642     # TO-DO: need to add a way for the user to extend this list for whatever
643     # auxiliary files they create in other (or their own) packages
644
645     # get path list from both env['TEXINPUTS'] and env['ENV']['TEXINPUTS']
646     savedpath = modify_env_var(env, 'TEXINPUTS', abspath)
647     paths = env['ENV']['TEXINPUTS']
648     if SCons.Util.is_List(paths):
649         pass
650     else:
651         # Split at os.pathsep to convert into absolute path
652         # TODO(1.5)
653         #paths = paths.split(os.pathsep)
654         paths = string.split(paths, os.pathsep)
655
656     # now that we have the path list restore the env
657     if savedpath is _null:
658         try:
659             del env['ENV']['TEXINPUTS']
660         except KeyError:
661             pass # was never set
662     else:
663         env['ENV']['TEXINPUTS'] = savedpath
664     if Verbose:
665         print "search path ",paths
666
667     aux_files = []
668     file_tests = ScanFiles(source[0], target, paths, file_tests, file_tests_search, env, graphics_extensions, targetdir, aux_files)
669
670     for (theSearch,suffix_list) in file_tests:
671         if theSearch:
672             for suffix in suffix_list:
673                 env.SideEffect(targetbase + suffix,target[0])
674                 if Verbose:
675                     print "side effect :",targetbase + suffix
676                 env.Clean(target[0],targetbase + suffix)
677
678     for aFile in aux_files:
679         aFile_base = SCons.Util.splitext(aFile)[0]
680         env.SideEffect(aFile_base + '.aux',target[0])
681         if Verbose:
682             print "side effect :",aFile_base + '.aux'
683         env.Clean(target[0],aFile_base + '.aux')
684     # read fls file to get all other files that latex creates and will read on the next pass
685     # remove files from list that we explicitly dealt with above
686     if os.path.exists(flsfilename) and os.path.isfile(flsfilename):
687         content = open(flsfilename, "rb").read()
688         out_files = openout_re.findall(content)
689         myfiles = [auxfilename, logfilename, flsfilename, targetbase+'.dvi',targetbase+'.pdf']
690         for filename in out_files[:]:
691             if filename in myfiles:
692                 out_files.remove(filename)
693         env.SideEffect(out_files,target[0])
694         if Verbose:
695             print "side effect :",out_files
696         env.Clean(target[0],out_files)
697
698     return (target, source)
699
700
701 TeXLaTeXAction = None
702
703 def generate(env):
704     """Add Builders and construction variables for TeX to an Environment."""
705
706     global TeXLaTeXAction
707     if TeXLaTeXAction is None:
708         TeXLaTeXAction = SCons.Action.Action(TeXLaTeXFunction,
709                               strfunction=TeXLaTeXStrFunction)
710
711     env.AppendUnique(LATEXSUFFIXES=SCons.Tool.LaTeXSuffixes)
712
713     generate_common(env)
714
715     import dvi
716     dvi.generate(env)
717
718     bld = env['BUILDERS']['DVI']
719     bld.add_action('.tex', TeXLaTeXAction)
720     bld.add_emitter('.tex', tex_eps_emitter)
721
722 def generate_common(env):
723     """Add internal Builders and construction variables for LaTeX to an Environment."""
724
725     # A generic tex file Action, sufficient for all tex files.
726     global TeXAction
727     if TeXAction is None:
728         TeXAction = SCons.Action.Action("$TEXCOM", "$TEXCOMSTR")
729
730     # An Action to build a latex file.  This might be needed more
731     # than once if we are dealing with labels and bibtex.
732     global LaTeXAction
733     if LaTeXAction is None:
734         LaTeXAction = SCons.Action.Action("$LATEXCOM", "$LATEXCOMSTR")
735
736     # Define an action to run BibTeX on a file.
737     global BibTeXAction
738     if BibTeXAction is None:
739         BibTeXAction = SCons.Action.Action("$BIBTEXCOM", "$BIBTEXCOMSTR")
740
741     # Define an action to run MakeIndex on a file.
742     global MakeIndexAction
743     if MakeIndexAction is None:
744         MakeIndexAction = SCons.Action.Action("$MAKEINDEXCOM", "$MAKEINDEXCOMSTR")
745
746     # Define an action to run MakeIndex on a file for nomenclatures.
747     global MakeNclAction
748     if MakeNclAction is None:
749         MakeNclAction = SCons.Action.Action("$MAKENCLCOM", "$MAKENCLCOMSTR")
750
751     # Define an action to run MakeIndex on a file for glossaries.
752     global MakeGlossaryAction
753     if MakeGlossaryAction is None:
754         MakeGlossaryAction = SCons.Action.Action("$MAKEGLOSSARYCOM", "$MAKEGLOSSARYCOMSTR")
755
756     # Define an action to run MakeIndex on a file for acronyms.
757     global MakeAcronymsAction
758     if MakeAcronymsAction is None:
759         MakeAcronymsAction = SCons.Action.Action("$MAKEACRONYMSCOM", "$MAKEACRONYMSCOMSTR")
760
761     env['TEX']      = 'tex'
762     env['TEXFLAGS'] = SCons.Util.CLVar('-interaction=nonstopmode -recorder')
763     env['TEXCOM']   = 'cd ${TARGET.dir} && $TEX $TEXFLAGS ${SOURCE.file}'
764
765     env['PDFTEX']      = 'pdftex'
766     env['PDFTEXFLAGS'] = SCons.Util.CLVar('-interaction=nonstopmode -recorder')
767     env['PDFTEXCOM']   = 'cd ${TARGET.dir} && $PDFTEX $PDFTEXFLAGS ${SOURCE.file}'
768
769     env['LATEX']        = 'latex'
770     env['LATEXFLAGS']   = SCons.Util.CLVar('-interaction=nonstopmode -recorder')
771     env['LATEXCOM']     = 'cd ${TARGET.dir} && $LATEX $LATEXFLAGS ${SOURCE.file}'
772     env['LATEXRETRIES'] = 3
773
774     env['PDFLATEX']      = 'pdflatex'
775     env['PDFLATEXFLAGS'] = SCons.Util.CLVar('-interaction=nonstopmode -recorder')
776     env['PDFLATEXCOM']   = 'cd ${TARGET.dir} && $PDFLATEX $PDFLATEXFLAGS ${SOURCE.file}'
777
778     env['BIBTEX']      = 'bibtex'
779     env['BIBTEXFLAGS'] = SCons.Util.CLVar('')
780     env['BIBTEXCOM']   = 'cd ${TARGET.dir} && $BIBTEX $BIBTEXFLAGS ${SOURCE.filebase}'
781
782     env['MAKEINDEX']      = 'makeindex'
783     env['MAKEINDEXFLAGS'] = SCons.Util.CLVar('')
784     env['MAKEINDEXCOM']   = 'cd ${TARGET.dir} && $MAKEINDEX $MAKEINDEXFLAGS ${SOURCE.file}'
785
786     env['MAKEGLOSSARY']      = 'makeindex'
787     env['MAKEGLOSSARYSTYLE'] = '${SOURCE.filebase}.ist'
788     env['MAKEGLOSSARYFLAGS'] = SCons.Util.CLVar('-s ${MAKEGLOSSARYSTYLE} -t ${SOURCE.filebase}.glg')
789     env['MAKEGLOSSARYCOM']   = 'cd ${TARGET.dir} && $MAKEGLOSSARY ${SOURCE.filebase}.glo $MAKEGLOSSARYFLAGS -o ${SOURCE.filebase}.gls'
790
791     env['MAKEACRONYMS']      = 'makeindex'
792     env['MAKEACRONYMSSTYLE'] = '${SOURCE.filebase}.ist'
793     env['MAKEACRONYMSFLAGS'] = SCons.Util.CLVar('-s ${MAKEACRONYMSSTYLE} -t ${SOURCE.filebase}.alg')
794     env['MAKEACRONYMSCOM']   = 'cd ${TARGET.dir} && $MAKEACRONYMS ${SOURCE.filebase}.acn $MAKEACRONYMSFLAGS -o ${SOURCE.filebase}.acr'
795
796     env['MAKENCL']      = 'makeindex'
797     env['MAKENCLSTYLE'] = 'nomencl.ist'
798     env['MAKENCLFLAGS'] = '-s ${MAKENCLSTYLE} -t ${SOURCE.filebase}.nlg'
799     env['MAKENCLCOM']   = 'cd ${TARGET.dir} && $MAKENCL ${SOURCE.filebase}.nlo $MAKENCLFLAGS -o ${SOURCE.filebase}.nls'
800
801 def exists(env):
802     return env.Detect('tex')
803
804 # Local Variables:
805 # tab-width:4
806 # indent-tabs-mode:nil
807 # End:
808 # vim: set expandtab tabstop=4 shiftwidth=4: