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