3 Tool-specific initialization for TeX.
5 There normally shouldn't be any need to import this module directly.
6 It will usually be imported through the generic SCons.Tool.Tool()
14 # Permission is hereby granted, free of charge, to any person obtaining
15 # a copy of this software and associated documentation files (the
16 # "Software"), to deal in the Software without restriction, including
17 # without limitation the rights to use, copy, modify, merge, publish,
18 # distribute, sublicense, and/or sell copies of the Software, and to
19 # permit persons to whom the Software is furnished to do so, subject to
20 # the following conditions:
22 # The above copyright notice and this permission notice shall be included
23 # in all copies or substantial portions of the Software.
25 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
26 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
27 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
45 warning_rerun_re = re.compile("^LaTeX Warning:.*Rerun", re.MULTILINE)
47 rerun_citations_str = "^LaTeX Warning:.*\n.*Rerun to get citations correct"
48 rerun_citations_re = re.compile(rerun_citations_str, re.MULTILINE)
50 undefined_references_str = '(^LaTeX Warning:.*undefined references)|(^Package \w+ Warning:.*undefined citations)'
51 undefined_references_re = re.compile(undefined_references_str, re.MULTILINE)
53 openout_aux_re = re.compile(r"\\openout.*`(.*\.aux)'")
55 # An Action sufficient to build any generic tex file.
58 # An action to build a latex file. This action might be needed more
59 # than once if we are dealing with labels and bibtex.
62 # An action to run BibTeX on a file.
65 # An action to run MakeIndex on a file.
66 MakeIndexAction = None
68 def InternalLaTeXAuxAction(XXXLaTeXAction, target = None, source= None, env=None):
69 """A builder for LaTeX files that checks the output in the aux file
70 and decides how many times to use LaTeXAction, and BibTeXAction."""
72 basename = SCons.Util.splitext(str(source[0]))[0]
73 basedir = os.path.split(str(source[0]))[0]
75 # Run LaTeX once to generate a new aux file.
76 XXXLaTeXAction(target, source, env)
78 # Decide if various things need to be run, or run again. We check
79 # for the existence of files before opening them--even ones like the
80 # aux file that TeX always creates--to make it possible to write tests
81 # with stubs that don't necessarily generate all of the same files.
83 # Read the log file to find all .aux files
84 logfilename = basename + '.log'
86 if os.path.exists(logfilename):
87 content = open(logfilename, "rb").read()
88 auxfiles = openout_aux_re.findall(content)
90 # Now decide if bibtex will need to be run.
91 for auxfilename in auxfiles:
92 if os.path.exists(os.path.join(basedir, auxfilename)):
93 content = open(os.path.join(basedir, auxfilename), "rb").read()
94 if string.find(content, "bibdata") != -1:
95 bibfile = env.fs.File(basename)
96 BibTeXAction(bibfile, bibfile, env)
99 # Now decide if makeindex will need to be run.
100 idxfilename = basename + '.idx'
101 if os.path.exists(idxfilename):
102 idxfile = env.fs.File(basename)
103 # TODO: if ( idxfile has changed) ...
104 MakeIndexAction(idxfile, idxfile, env)
105 XXXLaTeXAction(target, source, env)
107 # Now decide if latex will need to be run again due to table of contents.
108 tocfilename = basename + '.toc'
109 if os.path.exists(tocfilename):
110 # TODO: if ( tocfilename has changed) ...
111 XXXLaTeXAction(target, source, env)
113 # Now decide if latex needs to be run yet again.
114 logfilename = basename + '.log'
115 for trial in range(int(env.subst('$LATEXRETRIES'))):
116 if not os.path.exists(logfilename):
118 content = open(logfilename, "rb").read()
119 if not warning_rerun_re.search(content) and \
120 not rerun_citations_re.search(content) and \
121 not undefined_references_re.search(content):
123 XXXLaTeXAction(target, source, env)
126 def LaTeXAuxAction(target = None, source= None, env=None):
127 InternalLaTeXAuxAction( LaTeXAction, target, source, env )
129 LaTeX_re = re.compile("\\\\document(style|class)")
132 # Scan a file list to decide if it's TeX- or LaTeX-flavored.
134 content = f.get_contents()
135 if LaTeX_re.search(content):
139 def TeXLaTeXFunction(target = None, source= None, env=None):
140 """A builder for TeX and LaTeX that scans the source file to
141 decide the "flavor" of the source and then executes the appropriate
144 LaTeXAuxAction(target,source,env)
146 TeXAction(target,source,env)
149 def tex_emitter(target, source, env):
150 base = SCons.Util.splitext(str(source[0]))[0]
151 target.append(base + '.aux')
152 env.Precious(base + '.aux')
153 target.append(base + '.log')
155 content = f.get_contents()
156 if string.find(content, r'\tableofcontents') != -1:
157 target.append(base + '.toc')
158 if string.find(content, r'\makeindex') != -1:
159 target.append(base + '.ilg')
160 target.append(base + '.ind')
161 target.append(base + '.idx')
162 if string.find(content, r'\bibliography') != -1:
163 target.append(base + '.bbl')
164 target.append(base + '.blg')
166 # read log file to get all .aux files
167 logfilename = base + '.log'
168 if os.path.exists(logfilename):
169 content = open(logfilename, "rb").read()
170 aux_files = openout_aux_re.findall(content)
171 aux_files = filter(lambda f, b=base+'.aux': f != b, aux_files)
172 dir = os.path.split(base)[0]
173 aux_files = map(lambda f, d=dir: d+os.sep+f, aux_files)
174 target.extend(aux_files)
176 return (target, source)
178 TeXLaTeXAction = None
181 """Add Builders and construction variables for TeX to an Environment."""
183 # A generic tex file Action, sufficient for all tex files.
185 if TeXAction is None:
186 TeXAction = SCons.Action.Action("$TEXCOM", "$TEXCOMSTR")
188 # An Action to build a latex file. This might be needed more
189 # than once if we are dealing with labels and bibtex.
191 if LaTeXAction is None:
192 LaTeXAction = SCons.Action.Action("$LATEXCOM", "$LATEXCOMSTR")
194 # Define an action to run BibTeX on a file.
196 if BibTeXAction is None:
197 BibTeXAction = SCons.Action.Action("$BIBTEXCOM", "$BIBTEXCOMSTR")
199 # Define an action to run MakeIndex on a file.
200 global MakeIndexAction
201 if MakeIndexAction is None:
202 MakeIndexAction = SCons.Action.Action("$MAKEINDEXCOM", "$MAKEINDEXCOMSTR")
204 global TeXLaTeXAction
205 if TeXLaTeXAction is None:
206 TeXLaTeXAction = SCons.Action.Action(TeXLaTeXFunction, strfunction=None)
211 bld = env['BUILDERS']['DVI']
212 bld.add_action('.tex', TeXLaTeXAction)
213 bld.add_emitter('.tex', tex_emitter)
216 env['TEXFLAGS'] = SCons.Util.CLVar('')
217 env['TEXCOM'] = 'cd ${TARGET.dir} && $TEX $TEXFLAGS ${SOURCE.file}'
219 # Duplicate from latex.py. If latex.py goes away, then this is still OK.
220 env['LATEX'] = 'latex'
221 env['LATEXFLAGS'] = SCons.Util.CLVar('')
222 env['LATEXCOM'] = 'cd ${TARGET.dir} && $LATEX $LATEXFLAGS ${SOURCE.file}'
223 env['LATEXRETRIES'] = 3
225 env['BIBTEX'] = 'bibtex'
226 env['BIBTEXFLAGS'] = SCons.Util.CLVar('')
227 env['BIBTEXCOM'] = 'cd ${TARGET.dir} && $BIBTEX $BIBTEXFLAGS ${SOURCE.filebase}'
229 env['MAKEINDEX'] = 'makeindex'
230 env['MAKEINDEXFLAGS'] = SCons.Util.CLVar('')
231 env['MAKEINDEXCOM'] = 'cd ${TARGET.dir} && $MAKEINDEX $MAKEINDEXFLAGS ${SOURCE.file}'
234 return env.Detect('tex')