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