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)|(^Package \w+ 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)'")
54 openout_re = re.compile(r"\\openout.*`(.*)'")
56 makeindex_re = re.compile(r"^[^%]*\\makeindex", re.MULTILINE)
57 tableofcontents_re = re.compile(r"^[^%]*\\tableofcontents", re.MULTILINE)
58 bibliography_re = re.compile(r"^[^%]*\\bibliography", re.MULTILINE)
60 # An Action sufficient to build any generic tex file.
63 # An action to build a latex file. This action might be needed more
64 # than once if we are dealing with labels and bibtex.
67 # An action to run BibTeX on a file.
70 # An action to run MakeIndex on a file.
71 MakeIndexAction = None
73 def InternalLaTeXAuxAction(XXXLaTeXAction, target = None, source= None, env=None):
74 """A builder for LaTeX files that checks the output in the aux file
75 and decides how many times to use LaTeXAction, and BibTeXAction."""
77 basename = SCons.Util.splitext(str(source[0]))[0]
78 basedir = os.path.split(str(source[0]))[0]
79 basefile = os.path.split(str(basename))[1]
80 abspath = os.path.abspath(basedir)
81 targetbase = SCons.Util.splitext(str(target[0]))[0]
82 targetdir = os.path.split(str(target[0]))[0]
84 # Not sure if these environment changes should go here or make the
85 # user do them I undo all but TEXPICTS but there is still the side
86 # effect of creating the empty (':') entries in the environment.
88 def modify_env_var(env, var, abspath):
90 save = env['ENV'][var]
94 if SCons.Util.is_List(env['ENV'][var]):
95 env['ENV'][var] = [abspath] + env['ENV'][var]
97 env['ENV'][var] = abspath + os.pathsep + env['ENV'][var]
100 texinputs_save = modify_env_var(env, 'TEXINPUTS', abspath)
101 bibinputs_save = modify_env_var(env, 'BIBINPUTS', abspath)
102 bstinputs_save = modify_env_var(env, 'BSTINPUTS', abspath)
103 texpicts_save = modify_env_var(env, 'TEXPICTS', abspath)
105 # Create these file names with the target directory since they will
106 # be made there. That's because the *COM variables have the cd
107 # command in the prolog.
109 bblfilename = os.path.join(targetdir, basefile + '.bbl')
111 if os.path.exists(bblfilename):
112 bblContents = open(bblfilename, "rb").read()
114 idxfilename = os.path.join(targetdir, basefile + '.idx')
116 if os.path.exists(idxfilename):
117 idxContents = open(idxfilename, "rb").read()
119 tocfilename = os.path.join(targetdir, basefile + '.toc')
121 if os.path.exists(tocfilename):
122 tocContents = open(tocfilename, "rb").read()
124 # Run LaTeX once to generate a new aux file and log file.
125 XXXLaTeXAction(target, source, env)
127 # Decide if various things need to be run, or run again. We check
128 # for the existence of files before opening them--even ones like the
129 # aux file that TeX always creates--to make it possible to write tests
130 # with stubs that don't necessarily generate all of the same files.
132 # Read the log file to find all .aux files
133 logfilename = os.path.join(targetbase + '.log')
135 if os.path.exists(logfilename):
136 content = open(logfilename, "rb").read()
137 auxfiles = openout_aux_re.findall(content)
139 # Now decide if bibtex will need to be run.
140 for auxfilename in auxfiles:
141 target_aux = os.path.join(targetdir, auxfilename)
142 if os.path.exists(target_aux):
143 content = open(target_aux, "rb").read()
144 if string.find(content, "bibdata") != -1:
145 bibfile = env.fs.File(targetbase)
146 BibTeXAction(bibfile, bibfile, env)
150 # Now decide if latex will need to be run again due to table of contents.
151 if os.path.exists(tocfilename) and tocContents != open(tocfilename, "rb").read():
154 # Now decide if latex will need to be run again due to bibliography.
155 if os.path.exists(bblfilename) and bblContents != open(bblfilename, "rb").read():
158 # Now decide if latex will need to be run again due to index.
159 if os.path.exists(idxfilename) and idxContents != open(idxfilename, "rb").read():
160 # We must run makeindex
161 idxfile = env.fs.File(targetbase)
162 MakeIndexAction(idxfile, idxfile, env)
165 if must_rerun_latex == 1:
166 XXXLaTeXAction(target, source, env)
168 # Now decide if latex needs to be run yet again to resolve warnings.
169 logfilename = targetbase + '.log'
170 for _ in range(int(env.subst('$LATEXRETRIES'))):
171 if not os.path.exists(logfilename):
173 content = open(logfilename, "rb").read()
174 if not warning_rerun_re.search(content) and \
175 not rerun_citations_re.search(content) and \
176 not undefined_references_re.search(content):
178 XXXLaTeXAction(target, source, env)
180 env['ENV']['TEXINPUTS'] = texinputs_save
181 env['ENV']['BIBINPUTS'] = bibinputs_save
182 env['ENV']['BSTINPUTS'] = bibinputs_save
184 # The TEXPICTS enviroment variable is needed by a dvi -> pdf step
185 # later on Mac OSX so leave it,
186 # env['ENV']['TEXPICTS'] = texpicts_save
190 def LaTeXAuxAction(target = None, source= None, env=None):
191 InternalLaTeXAuxAction( LaTeXAction, target, source, env )
193 LaTeX_re = re.compile("\\\\document(style|class)")
196 # Scan a file list to decide if it's TeX- or LaTeX-flavored.
198 content = f.get_contents()
199 if LaTeX_re.search(content):
203 def TeXLaTeXFunction(target = None, source= None, env=None):
204 """A builder for TeX and LaTeX that scans the source file to
205 decide the "flavor" of the source and then executes the appropriate
208 LaTeXAuxAction(target,source,env)
210 TeXAction(target,source,env)
213 def tex_emitter(target, source, env):
214 base = SCons.Util.splitext(str(source[0]))[0]
215 targetbase = SCons.Util.splitext(str(target[0]))[0]
217 target.append(targetbase + '.aux')
218 env.Precious(targetbase + '.aux')
219 target.append(targetbase + '.log')
221 content = f.get_contents()
222 if tableofcontents_re.search(content):
223 target.append(targetbase + '.toc')
224 env.Precious(targetbase + '.toc')
225 if makeindex_re.search(content):
226 target.append(targetbase + '.ilg')
227 target.append(targetbase + '.ind')
228 target.append(targetbase + '.idx')
229 env.Precious(targetbase + '.idx')
230 if bibliography_re.search(content):
231 target.append(targetbase + '.bbl')
232 env.Precious(targetbase + '.bbl')
233 target.append(targetbase + '.blg')
235 # read log file to get all .aux files
236 logfilename = targetbase + '.log'
237 dir, base_nodir = os.path.split(targetbase)
238 if os.path.exists(logfilename):
239 content = open(logfilename, "rb").read()
240 out_files = openout_re.findall(content)
241 out_files = filter(lambda f, b=base_nodir+'.aux': f != b, out_files)
243 out_files = map(lambda f, d=dir: d+os.sep+f, out_files)
244 target.extend(out_files)
248 return (target, source)
250 TeXLaTeXAction = None
253 """Add Builders and construction variables for TeX to an Environment."""
255 # A generic tex file Action, sufficient for all tex files.
257 if TeXAction is None:
258 TeXAction = SCons.Action.Action("$TEXCOM", "$TEXCOMSTR")
260 # An Action to build a latex file. This might be needed more
261 # than once if we are dealing with labels and bibtex.
263 if LaTeXAction is None:
264 LaTeXAction = SCons.Action.Action("$LATEXCOM", "$LATEXCOMSTR")
266 # Define an action to run BibTeX on a file.
268 if BibTeXAction is None:
269 BibTeXAction = SCons.Action.Action("$BIBTEXCOM", "$BIBTEXCOMSTR")
271 # Define an action to run MakeIndex on a file.
272 global MakeIndexAction
273 if MakeIndexAction is None:
274 MakeIndexAction = SCons.Action.Action("$MAKEINDEXCOM", "$MAKEINDEXCOMSTR")
276 global TeXLaTeXAction
277 if TeXLaTeXAction is None:
278 TeXLaTeXAction = SCons.Action.Action(TeXLaTeXFunction, strfunction=None)
283 bld = env['BUILDERS']['DVI']
284 bld.add_action('.tex', TeXLaTeXAction)
285 bld.add_emitter('.tex', tex_emitter)
288 env['TEXFLAGS'] = SCons.Util.CLVar('')
289 env['TEXCOM'] = 'cd ${TARGET.dir} && $TEX $TEXFLAGS ${SOURCE.file}'
291 # Duplicate from latex.py. If latex.py goes away, then this is still OK.
292 env['LATEX'] = 'latex'
293 env['LATEXFLAGS'] = SCons.Util.CLVar('')
294 env['LATEXCOM'] = 'cd ${TARGET.dir} && $LATEX $LATEXFLAGS ${SOURCE.file}'
295 env['LATEXRETRIES'] = 3
297 env['BIBTEX'] = 'bibtex'
298 env['BIBTEXFLAGS'] = SCons.Util.CLVar('')
299 env['BIBTEXCOM'] = 'cd ${TARGET.dir} && $BIBTEX $BIBTEXFLAGS ${SOURCE.filebase}'
301 env['MAKEINDEX'] = 'makeindex'
302 env['MAKEINDEXFLAGS'] = SCons.Util.CLVar('')
303 env['MAKEINDEXCOM'] = 'cd ${TARGET.dir} && $MAKEINDEX $MAKEINDEXFLAGS ${SOURCE.file}'
306 return env.Detect('tex')