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