summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/engine/SCons/Scanner/LaTeX.py201
-rw-r--r--src/engine/SCons/Tool/__init__.py8
-rw-r--r--src/engine/SCons/Tool/pdf.py2
-rw-r--r--src/engine/SCons/Tool/tex.py459
-rw-r--r--test/TEX/LATEX.py12
-rw-r--r--test/TEX/bibtex-latex-rerun.py2
-rw-r--r--test/TEX/subdir_variantdir_input.py128
-rw-r--r--test/TEX/variant_dir_dup0.py77
8 files changed, 702 insertions, 187 deletions
diff --git a/src/engine/SCons/Scanner/LaTeX.py b/src/engine/SCons/Scanner/LaTeX.py
index ceb9bf5..c544108 100644
--- a/src/engine/SCons/Scanner/LaTeX.py
+++ b/src/engine/SCons/Scanner/LaTeX.py
@@ -31,19 +31,33 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import os.path
import string
+import re
import SCons.Scanner
def LaTeXScanner():
- """Return a prototype Scanner instance for scanning LaTeX source files"""
+ """Return a prototype Scanner instance for scanning LaTeX source files
+ when built with latex.
+ """
ds = LaTeX(name = "LaTeXScanner",
suffixes = '$LATEXSUFFIXES',
- path_variable = 'TEXINPUTS',
- regex = '\\\\(include|includegraphics(?:\[[^\]]+\])?|input|bibliography|usepackage){([^}]*)}',
+ # in the search order, see below in LaTeX class docstring
+ graphics_extensions = ['.eps', '.ps'],
recursive = 0)
return ds
-class LaTeX(SCons.Scanner.Classic):
+def PDFLaTeXScanner():
+ """Return a prototype Scanner instance for scanning LaTeX source files
+ when built with pdflatex.
+ """
+ ds = LaTeX(name = "PDFLaTeXScanner",
+ suffixes = '$LATEXSUFFIXES',
+ # in the search order, see below in LaTeX class docstring
+ graphics_extensions = ['.png', '.pdf', '.jpg', '.tif'],
+ recursive = 0)
+ return ds
+
+class LaTeX(SCons.Scanner.Base):
"""Class for scanning LaTeX files for included files.
Unlike most scanners, which use regular expressions that just
@@ -51,76 +65,191 @@ class LaTeX(SCons.Scanner.Classic):
of the keyword for the inclusion ("include", "includegraphics",
"input", or "bibliography"), and then the file name itself.
Based on a quick look at LaTeX documentation, it seems that we
- need a should append .tex suffix for the "include" keywords,
- append .tex if there is no extension for the "input" keyword,
- but leave the file name untouched for "includegraphics." For
- the "bibliography" keyword we need to add .bib if there is
- no extension. (This need to be revisited since if there
- is no extension for an "includegraphics" keyword latex will
- append .ps or .eps to find the file; while pdftex will use
- other extensions.)
+ should append .tex suffix for the "include" keywords, append .tex if
+ there is no extension for the "input" keyword, and need to add .bib
+ for the "bibliography" keyword that does not accept extensions by itself.
+
+ Finally, if there is no extension for an "includegraphics" keyword
+ latex will append .ps or .eps to find the file, while pdftex may use .pdf,
+ .jpg, .tif, .mps, or .png.
+
+ The actual subset and search order may be altered by
+ DeclareGraphicsExtensions command. This complication is ignored.
+ The default order corresponds to experimentation with teTeX
+ $ latex --version
+ pdfeTeX 3.141592-1.21a-2.2 (Web2C 7.5.4)
+ kpathsea version 3.5.4
+ The order is:
+ ['.eps', '.ps'] for latex
+ ['.png', '.pdf', '.jpg', '.tif'].
+
+ Another difference is that the search path is determined by the type
+ of the file being searched:
+ env['TEXINPUTS'] for "input" and "include" keywords
+ env['TEXPICTS'] for "includegraphics" keyword
+ env['BIBINPUTS'] for "bibliography" keyword
+ env['BSTINPUTS'] for "bibliographystyle" keyword
+
+ FIXME: also look for the class or style in document[class|style]{}
+ FIXME: also look for the argument of bibliographystyle{}
"""
- def latex_name(self, include):
+ keyword_paths = {'include': 'TEXINPUTS',
+ 'input': 'TEXINPUTS',
+ 'includegraphics': 'TEXPICTS',
+ 'bibliography': 'BIBINPUTS',
+ 'bibliographystyle': 'BSTINPUTS',
+ 'usepackage': 'TEXINPUTS'}
+ env_variables = SCons.Util.unique(keyword_paths.values())
+
+ def __init__(self, name, suffixes, graphics_extensions, *args, **kw):
+
+ regex = '\\\\(include|includegraphics(?:\[[^\]]+\])?|input|bibliography|usepackage){([^}]*)}'
+ self.cre = re.compile(regex, re.M)
+ self.graphics_extensions = graphics_extensions
+
+ def _scan(node, env, path=(), self=self):
+ node = node.rfile()
+ if not node.exists():
+ return []
+ return self.scan(node, path)
+
+ class FindMultiPathDirs:
+ """The stock FindPathDirs function has the wrong granularity:
+ it is called once per target, while we need the path that depends
+ on what kind of included files is being searched. This wrapper
+ hides multiple instances of FindPathDirs, one per the LaTeX path
+ variable in the environment. When invoked, the function calculates
+ and returns all the required paths as a dictionary (converted into
+ a tuple to become hashable). Then the scan function converts it
+ back and uses a dictionary of tuples rather than a single tuple
+ of paths.
+ """
+ def __init__(self, dictionary):
+ self.dictionary = {}
+ for k, n in dictionary.iteritems():
+ self.dictionary[k] = SCons.Scanner.FindPathDirs(n)
+ def __call__(self, env, dir=None, target=None, source=None, argument=None):
+ di = {}
+ for k, c in self.dictionary.iteritems():
+ p = c(env, dir=None, target=None, source=None, argument=None)
+ di[k] = p
+ # To prevent "dict is not hashable error"
+ rv = tuple([(k,v) for k, v in di.iteritems()])
+ return rv
+
+ class LaTeXScanCheck:
+ """Skip all but LaTeX source files, i.e., do not scan *.eps,
+ *.pdf, *.jpg, etc.
+ """
+ def __init__(self, suffixes):
+ self.suffixes = suffixes
+ def __call__(self, node, env):
+ current = not node.has_builder() or node.is_up_to_date()
+ scannable = node.get_suffix() in env.subst(self.suffixes)
+ # Returning false means that the file is not scanned.
+ return scannable and current
+
+ kw['function'] = _scan
+ kw['path_function'] = FindMultiPathDirs(LaTeX.keyword_paths)
+ kw['recursive'] = 1
+ kw['skeys'] = suffixes
+ kw['scan_check'] = LaTeXScanCheck(suffixes)
+ kw['name'] = name
+
+ apply(SCons.Scanner.Base.__init__, (self,) + args, kw)
+
+ def _latex_names(self, include):
filename = include[1]
if include[0] == 'input':
base, ext = os.path.splitext( filename )
if ext == "":
- filename = filename + '.tex'
+ return [filename + '.tex']
if (include[0] == 'include'):
- filename = filename + '.tex'
+ return [filename + '.tex']
if include[0] == 'bibliography':
base, ext = os.path.splitext( filename )
if ext == "":
- filename = filename + '.bib'
+ return [filename + '.bib']
if include[0] == 'usepackage':
base, ext = os.path.splitext( filename )
if ext == "":
- filename = filename + '.sty'
- return filename
+ return [filename + '.sty']
+ if include[0] == 'includegraphics':
+ base, ext = os.path.splitext( filename )
+ if ext == "":
+ return [filename + e for e in self.graphics_extensions]
+ return [filename]
+
def sort_key(self, include):
- return SCons.Node.FS._my_normcase(self.latex_name(include))
+ return SCons.Node.FS._my_normcase(str(include))
+
def find_include(self, include, source_dir, path):
- i = SCons.Node.FS.find_file(self.latex_name(include),
- (source_dir,) + path)
+ try:
+ sub_path = path[include[0]]
+ except:
+ sub_path = ()
+ try_names = self._latex_names(include)
+ for n in try_names:
+ i = SCons.Node.FS.find_file(n, (source_dir,) + sub_path)
+ if i:
+ return i, include
return i, include
def scan(self, node, path=()):
- #
# Modify the default scan function to allow for the regular
# expression to return a comma separated list of file names
# as can be the case with the bibliography keyword.
- #
- # cache the includes list in node so we only scan it once:
+
+ # Cache the includes list in node so we only scan it once:
+ path_dict = dict(list(path))
+ noopt_cre = re.compile('\[.*$')
if node.includes != None:
includes = node.includes
else:
includes = self.cre.findall(node.get_contents())
+ # 1. Split comma-separated lines, e.g.
+ # ('bibliography', 'phys,comp')
+ # should become two entries
+ # ('bibliography', 'phys')
+ # ('bibliography', 'comp')
+ # 2. Remove the options, e.g., such as
+ # ('includegraphics[clip,width=0.7\\linewidth]', 'picture.eps')
+ # should become
+ # ('includegraphics', 'picture.eps')
+ split_includes = []
+ for include in includes:
+ inc_type = noopt_cre.sub('', include[0])
+ inc_list = string.split(include[1],',')
+ for j in range(len(inc_list)):
+ split_includes.append( (inc_type, inc_list[j]) )
+ #
+ includes = split_includes
node.includes = includes
# This is a hand-coded DSU (decorate-sort-undecorate, or
# Schwartzian transform) pattern. The sort key is the raw name
- # of the file as specifed on the #include line (including the
- # " or <, since that may affect what file is found), which lets
+ # of the file as specifed on the \include, \input, etc. line.
+ # TODO: what about the comment in the original Classic scanner:
+ # """which lets
# us keep the sort order constant regardless of whether the file
- # is actually found in a Repository or locally.
+ # is actually found in a Repository or locally."""
nodes = []
source_dir = node.get_dir()
for include in includes:
#
# Handle multiple filenames in include[1]
#
- inc_list = string.split(include[1],',')
- for j in range(len(inc_list)):
- include_local = [include[0],inc_list[j]]
- n, i = self.find_include(include_local, source_dir, path)
-
+ n, i = self.find_include(include, source_dir, path_dict)
if n is None:
- SCons.Warnings.warn(SCons.Warnings.DependencyWarning,
- "No dependency generated for file: %s (included from: %s) -- file not found" % (i, node))
+ # Do not bother with 'usepackage' warnings, as they most
+ # likely refer to system-level files
+ if include[0] != 'usepackage':
+ SCons.Warnings.warn(SCons.Warnings.DependencyWarning,
+ "No dependency generated for file: %s (included from: %s) -- file not found" % (i, node))
else:
- sortkey = self.sort_key(include)
+ sortkey = self.sort_key(n)
nodes.append((sortkey, n))
-
+ #
nodes.sort()
nodes = map(lambda pair: pair[1], nodes)
return nodes
diff --git a/src/engine/SCons/Tool/__init__.py b/src/engine/SCons/Tool/__init__.py
index f197b68..9924ab1 100644
--- a/src/engine/SCons/Tool/__init__.py
+++ b/src/engine/SCons/Tool/__init__.py
@@ -55,6 +55,7 @@ DefaultToolpath=[]
CScanner = SCons.Scanner.C.CScanner()
DScanner = SCons.Scanner.D.DScanner()
LaTeXScanner = SCons.Scanner.LaTeX.LaTeXScanner()
+PDFLaTeXScanner = SCons.Scanner.LaTeX.PDFLaTeXScanner()
ProgramScanner = SCons.Scanner.Prog.ProgramScanner()
SourceFileScanner = SCons.Scanner.Base({}, name='SourceFileScanner')
@@ -76,8 +77,13 @@ for suffix in CSuffixes:
for suffix in DSuffixes:
SourceFileScanner.add_scanner(suffix, DScanner)
+# FIXME: what should be done here? Two scanners scan the same extensions,
+# but look for different files, e.g., "picture.eps" vs. "picture.pdf".
+# The builders for DVI and PDF explicitly reference their scanners
+# I think that means this is not needed???
for suffix in LaTeXSuffixes:
- SourceFileScanner.add_scanner(suffix, LaTeXScanner)
+ SourceFileScanner.add_scanner(suffix, LaTeXScanner)
+ SourceFileScanner.add_scanner(suffix, PDFLaTeXScanner)
class Tool:
def __init__(self, name, toolpath=[], **kw):
diff --git a/src/engine/SCons/Tool/pdf.py b/src/engine/SCons/Tool/pdf.py
index b0cd126..37c4c01 100644
--- a/src/engine/SCons/Tool/pdf.py
+++ b/src/engine/SCons/Tool/pdf.py
@@ -41,7 +41,7 @@ def generate(env):
global PDFBuilder
if PDFBuilder is None:
PDFBuilder = SCons.Builder.Builder(action = {},
- source_scanner = SCons.Tool.LaTeXScanner,
+ source_scanner = SCons.Tool.PDFLaTeXScanner,
prefix = '$PDFPREFIX',
suffix = '$PDFSUFFIX',
emitter = {},
diff --git a/src/engine/SCons/Tool/tex.py b/src/engine/SCons/Tool/tex.py
index db083ec..59980e5 100644
--- a/src/engine/SCons/Tool/tex.py
+++ b/src/engine/SCons/Tool/tex.py
@@ -43,20 +43,53 @@ import SCons.Node
import SCons.Node.FS
import SCons.Util
-warning_rerun_re = re.compile('(^LaTeX Warning:.*Rerun)|(^Package \w+ Warning:.*Rerun)', re.MULTILINE)
+Verbose = False
+must_rerun_latex = True
+
+# these are files that just need to be checked for changes and then rerun latex
+check_suffixes = ['.toc', '.lof', '.lot', '.out', '.nav', '.snm']
+
+# these are files that require bibtex or makeindex to be run when they change
+all_suffixes = check_suffixes + ['.bbl', '.idx', '.nlo', '.glo']
+
+#
+# regular expressions used to search for Latex features
+# or outputs that require rerunning latex
+#
+# search for all .aux files opened by latex (recorded in the .log file)
+openout_aux_re = re.compile(r"\\openout.*`(.*\.aux)'")
+
+#printindex_re = re.compile(r"^[^%]*\\printindex", re.MULTILINE)
+#printnomenclature_re = re.compile(r"^[^%]*\\printnomenclature", re.MULTILINE)
+#printglossary_re = re.compile(r"^[^%]*\\printglossary", re.MULTILINE)
+
+# search to find rerun warnings
+warning_rerun_str = '(^LaTeX Warning:.*Rerun)|(^Package \w+ Warning:.*Rerun)'
+warning_rerun_re = re.compile(warning_rerun_str, re.MULTILINE)
+
+# search to find citation rerun warnings
rerun_citations_str = "^LaTeX Warning:.*\n.*Rerun to get citations correct"
rerun_citations_re = re.compile(rerun_citations_str, re.MULTILINE)
+# search to find undefined references or citations warnings
undefined_references_str = '(^LaTeX Warning:.*undefined references)|(^Package \w+ Warning:.*undefined citations)'
undefined_references_re = re.compile(undefined_references_str, re.MULTILINE)
-openout_aux_re = re.compile(r"\\openout.*`(.*\.aux)'")
-openout_re = re.compile(r"\\openout.*`(.*)'")
-
-makeindex_re = re.compile(r"^[^%]*\\makeindex", re.MULTILINE)
+# used by the emitter
+auxfile_re = re.compile(r".", re.MULTILINE)
tableofcontents_re = re.compile(r"^[^%]*\\tableofcontents", re.MULTILINE)
+makeindex_re = re.compile(r"^[^%]*\\makeindex", re.MULTILINE)
bibliography_re = re.compile(r"^[^%]*\\bibliography", re.MULTILINE)
+listoffigures_re = re.compile(r"^[^%]*\\listoffigures", re.MULTILINE)
+listoftables_re = re.compile(r"^[^%]*\\listoftables", re.MULTILINE)
+hyperref_re = re.compile(r"^[^%]*\\usepackage.*\{hyperref\}", re.MULTILINE)
+makenomenclature_re = re.compile(r"^[^%]*\\makenomenclature", re.MULTILINE)
+makeglossary_re = re.compile(r"^[^%]*\\makeglossary", re.MULTILINE)
+beamer_re = re.compile(r"^[^%]*\\documentclass\{beamer\}", re.MULTILINE)
+
+# search to find all files opened by Latex (recorded in .log file)
+openout_re = re.compile(r"\\openout.*`(.*)'")
# An Action sufficient to build any generic tex file.
TeXAction = None
@@ -71,10 +104,51 @@ BibTeXAction = None
# An action to run MakeIndex on a file.
MakeIndexAction = None
+# An action to run MakeIndex (for nomencl) on a file.
+MakeNclAction = None
+
+# An action to run MakeIndex (for glossary) on a file.
+MakeGlossaryAction = None
+
+# Used as a return value of modify_env_var if the variable is not set.
+class _Null:
+ pass
+_null = _Null
+
+# The user specifies the paths in env[variable], similar to other builders.
+# They may be relative and must be converted to absolute, as expected
+# by LaTeX and Co. The environment may already have some paths in
+# env['ENV'][var]. These paths are honored, but the env[var] paths have
+# higher precedence. All changes are un-done on exit.
+def modify_env_var(env, var, abspath):
+ try:
+ save = env['ENV'][var]
+ except KeyError:
+ save = _null
+ env.PrependENVPath(var, abspath)
+ try:
+ if SCons.Util.is_List(env[var]):
+ env.PrependENVPath(var, [os.path.abspath(str(p)) for p in env[var]])
+ else:
+ # Split at os.pathsep to convert into absolute path
+ env.PrependENVPath(var, [os.path.abspath(p) for p in str(env[var]).split(os.pathsep)])
+ except KeyError:
+ pass
+ # Convert into a string explicitly to append ":" (without which it won't search system
+ # paths as well). The problem is that env.AppendENVPath(var, ":")
+ # does not work, refuses to append ":" (os.pathsep).
+ if SCons.Util.is_List(env['ENV'][var]):
+ env['ENV'][var] = os.pathsep.join(env['ENV'][var])
+ # Append the trailing os.pathsep character here to catch the case with no env[var]
+ env['ENV'][var] = env['ENV'][var] + os.pathsep
+ return save
+
def InternalLaTeXAuxAction(XXXLaTeXAction, target = None, source= None, env=None):
"""A builder for LaTeX files that checks the output in the aux file
and decides how many times to use LaTeXAction, and BibTeXAction."""
+ global must_rerun_latex
+
# This routine is called with two actions. In this file for DVI builds
# with LaTeXAction and from the pdflatex.py with PDFLaTeXAction
# set this up now for the case where the user requests a different extension
@@ -88,139 +162,188 @@ def InternalLaTeXAuxAction(XXXLaTeXAction, target = None, source= None, env=None
basedir = os.path.split(str(source[0]))[0]
basefile = os.path.split(str(basename))[1]
abspath = os.path.abspath(basedir)
- targetbase = SCons.Util.splitext(str(target[0]))[0]
targetext = os.path.splitext(str(target[0]))[1]
targetdir = os.path.split(str(target[0]))[0]
- # Not sure if these environment changes should go here or make the
- # user do them I undo all but TEXPICTS but there is still the side
- # effect of creating the empty (':') entries in the environment.
-
- def modify_env_var(env, var, abspath):
- try:
- save = env['ENV'][var]
- except KeyError:
- save = ':'
- env['ENV'][var] = ''
- if SCons.Util.is_List(env['ENV'][var]):
- env['ENV'][var] = [abspath] + env['ENV'][var]
- else:
- env['ENV'][var] = abspath + os.pathsep + env['ENV'][var]
- return save
-
- texinputs_save = modify_env_var(env, 'TEXINPUTS', abspath)
- bibinputs_save = modify_env_var(env, 'BIBINPUTS', abspath)
- bstinputs_save = modify_env_var(env, 'BSTINPUTS', abspath)
- texpicts_save = modify_env_var(env, 'TEXPICTS', abspath)
-
- # Create these file names with the target directory since they will
- # be made there. That's because the *COM variables have the cd
- # command in the prolog.
-
- bblfilename = os.path.join(targetdir, basefile + '.bbl')
- bblContents = ""
- if os.path.exists(bblfilename):
- bblContents = open(bblfilename, "rb").read()
-
- idxfilename = os.path.join(targetdir, basefile + '.idx')
- idxContents = ""
- if os.path.exists(idxfilename):
- idxContents = open(idxfilename, "rb").read()
-
- tocfilename = os.path.join(targetdir, basefile + '.toc')
- tocContents = ""
- if os.path.exists(tocfilename):
- tocContents = open(tocfilename, "rb").read()
-
- # generate the file name that latex will generate
- resultfilename = os.path.join(targetdir, basefile + targetext)
+ saved_env = {}
+ for var in SCons.Scanner.LaTeX.LaTeX.env_variables:
+ saved_env[var] = modify_env_var(env, var, abspath)
- # Run LaTeX once to generate a new aux file and log file.
- result = XXXLaTeXAction(target, source, env)
- if result != 0:
- return result
-
- # Decide if various things need to be run, or run again. We check
+ # Create base file names with the target directory since the auxiliary files
+ # will be made there. That's because the *COM variables have the cd
+ # command in the prolog. We check
# for the existence of files before opening them--even ones like the
# aux file that TeX always creates--to make it possible to write tests
# with stubs that don't necessarily generate all of the same files.
- # Read the log file to find all .aux files
- logfilename = os.path.join(targetbase + '.log')
- auxfiles = []
- if os.path.exists(logfilename):
- content = open(logfilename, "rb").read()
- auxfiles = openout_aux_re.findall(content)
-
- # Now decide if bibtex will need to be run.
- for auxfilename in auxfiles:
- target_aux = os.path.join(targetdir, auxfilename)
- if os.path.exists(target_aux):
- content = open(target_aux, "rb").read()
- if string.find(content, "bibdata") != -1:
- bibfile = env.fs.File(targetbase)
- result = BibTeXAction(bibfile, bibfile, env)
- if result != 0:
- return result
- break
-
- must_rerun_latex = 0
- # Now decide if latex will need to be run again due to table of contents.
- if os.path.exists(tocfilename) and tocContents != open(tocfilename, "rb").read():
- must_rerun_latex = 1
-
- # Now decide if latex will need to be run again due to bibliography.
- if os.path.exists(bblfilename) and bblContents != open(bblfilename, "rb").read():
- must_rerun_latex = 1
-
- # Now decide if latex will need to be run again due to index.
- if os.path.exists(idxfilename) and idxContents != open(idxfilename, "rb").read():
- # We must run makeindex
- idxfile = env.fs.File(targetbase)
- result = MakeIndexAction(idxfile, idxfile, env)
- if result != 0:
- return result
- must_rerun_latex = 1
+ targetbase = os.path.join(targetdir, basefile)
+
+ # if there is a \makeindex there will be a .idx and thus
+ # we have to run makeindex at least once to keep the build
+ # happy even if there is no index.
+ # Same for glossaries and nomenclature
+ src_content = source[0].get_contents()
+ run_makeindex = makeindex_re.search(src_content) and not os.path.exists(targetbase + '.idx')
+ run_nomenclature = makenomenclature_re.search(src_content) and not os.path.exists(targetbase + '.nlo')
+ run_glossary = makeglossary_re.search(src_content) and not os.path.exists(targetbase + '.glo')
+
+ saved_hashes = {}
+ suffix_nodes = {}
+
+ for suffix in all_suffixes:
+ theNode = env.fs.File(targetbase + suffix)
+ suffix_nodes[suffix] = theNode
+ saved_hashes[suffix] = theNode.get_csig()
+
+ if Verbose:
+ print "hashes: ",saved_hashes
+
+ must_rerun_latex = True
+
+ #
+ # routine to update MD5 hash and compare
+ #
+ def check_MD5(filenode, suffix):
+ global must_rerun_latex
+ # two calls to clear old csig
+ filenode.clear_memoized_values()
+ filenode.ninfo = filenode.new_ninfo()
+ new_md5 = filenode.get_csig()
+
+ if saved_hashes[suffix] == new_md5:
+ if Verbose:
+ print "file %s not changed" % (targetbase+suffix)
+ return False # unchanged
+ saved_hashes[suffix] = new_md5
+ must_rerun_latex = True
+ if Verbose:
+ print "file %s changed, rerunning Latex, new hash = " % (targetbase+suffix), new_md5
+ return True # changed
- if must_rerun_latex == 1:
- result = XXXLaTeXAction(target, source, env)
- if result != 0:
- return result
+ # generate the file name that latex will generate
+ resultfilename = targetbase + callerSuffix
- # Now decide if latex needs to be run yet again to resolve warnings.
- logfilename = targetbase + '.log'
- for _ in range(int(env.subst('$LATEXRETRIES'))):
- if not os.path.exists(logfilename):
- break
- content = open(logfilename, "rb").read()
- if not warning_rerun_re.search(content) and \
- not rerun_citations_re.search(content) and \
- not undefined_references_re.search(content):
- break
+ count = 0
+
+ while (must_rerun_latex and count < int(env.subst('$LATEXRETRIES'))) :
result = XXXLaTeXAction(target, source, env)
if result != 0:
return result
+ count = count + 1
+
+ must_rerun_latex = False
+ # Decide if various things need to be run, or run again.
+
+ # Read the log file to find all .aux files
+ logfilename = targetbase + '.log'
+ logContent = ''
+ auxfiles = []
+ if os.path.exists(logfilename):
+ logContent = open(logfilename, "rb").read()
+ auxfiles = openout_aux_re.findall(logContent)
+
+ # Now decide if bibtex will need to be run.
+ # The information that bibtex reads from the .aux file is
+ # pass-independent. If we find (below) that the .bbl file is unchanged,
+ # then the last latex saw a correct bibliography.
+ # Therefore only do this on the first pass
+ if count == 1:
+ for auxfilename in auxfiles:
+ target_aux = os.path.join(targetdir, auxfilename)
+ if os.path.exists(target_aux):
+ content = open(target_aux, "rb").read()
+ if string.find(content, "bibdata") != -1:
+ if Verbose:
+ print "Need to run bibtex"
+ bibfile = env.fs.File(targetbase)
+ result = BibTeXAction(bibfile, bibfile, env)
+ if result != 0:
+ return result
+ must_rerun_latex = check_MD5(suffix_nodes['.bbl'],'.bbl')
+ break
+
+ # Now decide if latex will need to be run again due to index.
+ if check_MD5(suffix_nodes['.idx'],'.idx') or (count == 1 and run_makeindex):
+ # We must run makeindex
+ if Verbose:
+ print "Need to run makeindex"
+ idxfile = suffix_nodes['.idx']
+ result = MakeIndexAction(idxfile, idxfile, env)
+ if result != 0:
+ return result
+
+ # TO-DO: need to add a way for the user to extend this list for whatever
+ # auxiliary files they create in other (or their own) packages
+ # Harder is case is where an action needs to be called -- that should be rare (I hope?)
+
+ for index in check_suffixes:
+ check_MD5(suffix_nodes[index],index)
+
+ # Now decide if latex will need to be run again due to nomenclature.
+ if check_MD5(suffix_nodes['.nlo'],'.nlo') or (count == 1 and run_nomenclature):
+ # We must run makeindex
+ if Verbose:
+ print "Need to run makeindex for nomenclature"
+ nclfile = suffix_nodes['.nlo']
+ result = MakeNclAction(nclfile, nclfile, env)
+ if result != 0:
+ return result
+
+ # Now decide if latex will need to be run again due to glossary.
+ if check_MD5(suffix_nodes['.glo'],'.glo') or (count == 1 and run_glossary):
+ # We must run makeindex
+ if Verbose:
+ print "Need to run makeindex for glossary"
+ glofile = suffix_nodes['.glo']
+ result = MakeGlossaryAction(glofile, glofile, env)
+ if result != 0:
+ return result
+
+ # Now decide if latex needs to be run yet again to resolve warnings.
+ if warning_rerun_re.search(logContent):
+ must_rerun_latex = True
+ if Verbose:
+ print "rerun Latex due to latex or package rerun warning"
+
+ if rerun_citations_re.search(logContent):
+ must_rerun_latex = True
+ if Verbose:
+ print "rerun Latex due to 'Rerun to get citations correct' warning"
+
+ if undefined_references_re.search(logContent):
+ must_rerun_latex = True
+ if Verbose:
+ print "rerun Latex due to undefined references or citations"
+
+ if (count >= int(env.subst('$LATEXRETRIES')) and must_rerun_latex):
+ print "reached max number of retries on Latex ,",int(env.subst('$LATEXRETRIES'))
+# end of while loop
+
# rename Latex's output to what the target name is
if not (str(target[0]) == resultfilename and os.path.exists(resultfilename)):
if os.path.exists(resultfilename):
print "move %s to %s" % (resultfilename, str(target[0]), )
shutil.move(resultfilename,str(target[0]))
- # if the user gave some other extension try PDFSUFFIX and then .dvi
- # not sure how to tell if we got here from a PDF or DVI builder.
- else:
- resultfilename = os.path.splitext(resultfilename)[0] + callerSuffix
- if os.path.exists(resultfilename):
- print "move %s to %s" % (resultfilename, str(target[0]), )
- shutil.move(resultfilename,str(target[0]))
-
- env['ENV']['TEXINPUTS'] = texinputs_save
- env['ENV']['BIBINPUTS'] = bibinputs_save
- env['ENV']['BSTINPUTS'] = bibinputs_save
+ # Original comment (when TEXPICTS was not restored):
# The TEXPICTS enviroment variable is needed by a dvi -> pdf step
- # later on Mac OSX so leave it,
- # env['ENV']['TEXPICTS'] = texpicts_save
+ # later on Mac OSX so leave it
+ #
+ # It is also used when searching for pictures (implicit dependencies).
+ # Why not set the variable again in the respective builder instead
+ # of leaving local modifications in the environment? What if multiple
+ # latex builds in different directories need different TEXPICTS?
+ for var in SCons.Scanner.LaTeX.LaTeX.env_variables:
+ if var == 'TEXPICTS':
+ continue
+ if saved_env[var] is _null:
+ try:
+ env['ENV'].pop(var)
+ except KeyError:
+ pass # was never set
+ else:
+ env['ENV'][var] = saved_env[var]
return result
@@ -262,42 +385,65 @@ def TeXLaTeXStrFunction(target = None, source= None, env=None):
return result
def tex_emitter(target, source, env):
- base = SCons.Util.splitext(str(source[0]))[0]
+ """An emitter for TeX and LaTeX sources.
+ For LaTeX sources we try and find the common created files that
+ are needed on subsequent runs of latex to finish tables of contents,
+ bibliographies, indices, lists of figures, and hyperlink references.
+ """
targetbase = SCons.Util.splitext(str(target[0]))[0]
+ basename = SCons.Util.splitext(str(source[0]))[0]
+ basefile = os.path.split(str(basename))[1]
- target.append(targetbase + '.aux')
- env.Precious(targetbase + '.aux')
- target.append(targetbase + '.log')
- for f in source:
- content = f.get_contents()
- if tableofcontents_re.search(content):
- target.append(targetbase + '.toc')
- env.Precious(targetbase + '.toc')
- if makeindex_re.search(content):
- target.append(targetbase + '.ilg')
- target.append(targetbase + '.ind')
- target.append(targetbase + '.idx')
- env.Precious(targetbase + '.idx')
- if bibliography_re.search(content):
- target.append(targetbase + '.bbl')
- env.Precious(targetbase + '.bbl')
- target.append(targetbase + '.blg')
-
- # read log file to get all .aux files
+ #
+ # file names we will make use of in searching the sources and log file
+ #
+ emit_suffixes = ['.aux', '.log', '.ilg', '.blg', '.nls', '.nlg', '.gls', '.glg'] + all_suffixes
+ auxfilename = targetbase + '.aux'
logfilename = targetbase + '.log'
- dir, base_nodir = os.path.split(targetbase)
+
+ env.SideEffect(auxfilename,target[0])
+ env.SideEffect(logfilename,target[0])
+ env.Clean(target[0],auxfilename)
+ env.Clean(target[0],logfilename)
+
+ content = source[0].get_contents()
+ idx_exists = os.path.exists(targetbase + '.idx')
+ nlo_exists = os.path.exists(targetbase + '.nlo')
+ glo_exists = os.path.exists(targetbase + '.glo')
+
+ file_tests = [(auxfile_re.search(content),['.aux']),
+ (makeindex_re.search(content) or idx_exists,['.idx', '.ind', '.ilg']),
+ (bibliography_re.search(content),['.bbl', '.blg']),
+ (tableofcontents_re.search(content),['.toc']),
+ (listoffigures_re.search(content),['.lof']),
+ (listoftables_re.search(content),['.lot']),
+ (hyperref_re.search(content),['.out']),
+ (makenomenclature_re.search(content) or nlo_exists,['.nlo', '.nls', '.nlg']),
+ (makeglossary_re.search(content) or glo_exists,['.glo', '.gls', '.glg']),
+ (beamer_re.search(content),['.nav', '.snm', '.out', '.toc']) ]
+ # Note we add the various makeindex files if the file produced by latex exists (.idx, .glo, .nlo)
+ # This covers the case where the \makeindex, \makenomenclature, or \makeglossary
+ # is not in the main file but we want to clean the files and those made by makeindex
+
+ # TO-DO: need to add a way for the user to extend this list for whatever
+ # auxiliary files they create in other (or their own) packages
+
+ for (theSearch,suffix_list) in file_tests:
+ if theSearch:
+ for suffix in suffix_list:
+ env.SideEffect(targetbase + suffix,target[0])
+ env.Clean(target[0],targetbase + suffix)
+
+ # read log file to get all other files that latex creates and will read on the next pass
if os.path.exists(logfilename):
content = open(logfilename, "rb").read()
out_files = openout_re.findall(content)
- out_files = filter(lambda f, b=base_nodir+'.aux': f != b, out_files)
- if dir != '':
- out_files = map(lambda f, d=dir: d+os.sep+f, out_files)
- target.extend(out_files)
- for f in out_files:
- env.Precious( f )
+ env.SideEffect(out_files,target[0])
+ env.Clean(target[0],out_files)
return (target, source)
+
TeXLaTeXAction = None
def generate(env):
@@ -324,6 +470,16 @@ def generate(env):
if MakeIndexAction is None:
MakeIndexAction = SCons.Action.Action("$MAKEINDEXCOM", "$MAKEINDEXCOMSTR")
+ # Define an action to run MakeIndex on a file for nomenclatures.
+ global MakeNclAction
+ if MakeNclAction is None:
+ MakeNclAction = SCons.Action.Action("$MAKENCLCOM", "$MAKENCLCOMSTR")
+
+ # Define an action to run MakeIndex on a file for glossaries.
+ global MakeGlossaryAction
+ if MakeGlossaryAction is None:
+ MakeGlossaryAction = SCons.Action.Action("$MAKEGLOSSARYCOM", "$MAKEGLOSSARYCOMSTR")
+
global TeXLaTeXAction
if TeXLaTeXAction is None:
TeXLaTeXAction = SCons.Action.Action(TeXLaTeXFunction,
@@ -354,5 +510,20 @@ def generate(env):
env['MAKEINDEXFLAGS'] = SCons.Util.CLVar('')
env['MAKEINDEXCOM'] = 'cd ${TARGET.dir} && $MAKEINDEX $MAKEINDEXFLAGS ${SOURCE.file}'
+ env['MAKEGLOSSARY'] = 'makeindex'
+ env['MAKEGLOSSARYSTYLE'] = '${SOURCE.filebase}.ist'
+ env['MAKEGLOSSARYFLAGS'] = SCons.Util.CLVar('-s ${MAKEGLOSSARYSTYLE} -t ${SOURCE.filebase}.glg')
+ env['MAKEGLOSSARYCOM'] = 'cd ${TARGET.dir} && $MAKEGLOSSARY ${SOURCE.filebase}.glo $MAKEGLOSSARYFLAGS -o ${SOURCE.filebase}.gls'
+
+ env['MAKENCL'] = 'makeindex'
+ env['MAKENCLSTYLE'] = '$nomencl.ist'
+ env['MAKENCLFLAGS'] = '-s ${MAKENCLSTYLE} -t ${SOURCE.filebase}.nlg'
+ env['MAKENCLCOM'] = 'cd ${TARGET.dir} && $MAKENCL ${SOURCE.filebase}.nlo $MAKENCLFLAGS -o ${SOURCE.filebase}.nls'
+
+ # Duplicate from pdflatex.py. If latex.py goes away, then this is still OK.
+ env['PDFLATEX'] = 'pdflatex'
+ env['PDFLATEXFLAGS'] = SCons.Util.CLVar('-interaction=nonstopmode')
+ env['PDFLATEXCOM'] = 'cd ${TARGET.dir} && $PDFLATEX $PDFLATEXFLAGS ${SOURCE.file}'
+
def exists(env):
return env.Detect('tex')
diff --git a/test/TEX/LATEX.py b/test/TEX/LATEX.py
index bb034be..34c5d13 100644
--- a/test/TEX/LATEX.py
+++ b/test/TEX/LATEX.py
@@ -146,7 +146,7 @@ This is the %s LaTeX file.
latex1 = r"""
\documentclass{report}
\usepackage{makeidx}
-\makeindex
+\input{latexinputfile}
\begin{document}
\index{info}
This is the %s LaTeX file.
@@ -157,6 +157,10 @@ It has an Index and includes another file.
"""
latex2 = r"""
+\makeindex
+"""
+
+ latex3 = r"""
\index{include}
This is the include file.
\printindex{}
@@ -171,7 +175,8 @@ This is the include file.
test.subdir('subdir')
test.write('latexi.tex', latex1 % 'latexi.tex');
- test.write([ 'subdir', 'latexincludefile.tex'], latex2)
+ test.write([ 'subdir', 'latexinputfile.tex'], latex2)
+ test.write([ 'subdir', 'latexincludefile.tex'], latex3)
test.run(arguments = 'foo.dvi', stderr = None)
test.must_not_exist('wrapper.out')
@@ -188,6 +193,9 @@ This is the include file.
test.must_exist('latexi.dvi')
test.must_exist('latexi.ind')
+ test.run(arguments = '-c', stderr = None)
+ test.must_not_exist('latexi.ind')
+ test.must_not_exist('latexi.ilg')
test.pass_test()
diff --git a/test/TEX/bibtex-latex-rerun.py b/test/TEX/bibtex-latex-rerun.py
index 9191713..bd1fd3b 100644
--- a/test/TEX/bibtex-latex-rerun.py
+++ b/test/TEX/bibtex-latex-rerun.py
@@ -42,7 +42,7 @@ if not pdflatex:
test.skip_test("Could not find pdflatex; skipping test(s).\n")
test.write(['SConstruct'], """\
-env = Environment(tools=['pdflatex', 'tex'])
+env = Environment(tools=['pdftex', 'tex'])
env.PDF( 'bibtest.tex' )
""")
diff --git a/test/TEX/subdir_variantdir_input.py b/test/TEX/subdir_variantdir_input.py
new file mode 100644
index 0000000..1229032
--- /dev/null
+++ b/test/TEX/subdir_variantdir_input.py
@@ -0,0 +1,128 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that we execute TeX in a subdirectory (if that's where the document
+resides) by checking that all the auxiliary files get created there and
+not in the top-level directory. Test this when variantDir is used
+
+Also check that we find files
+
+Test case courtesy Joel B. Mohler.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+#test.verbose_set(2)
+
+latex = test.where_is('latex')
+if not latex:
+ test.skip_test("Could not find 'latex'; skipping test.\n")
+
+pdflatex = test.where_is('pdflatex')
+if not pdflatex:
+ test.skip_test("Could not find 'pdflatex'; skipping test.\n")
+
+test.subdir('docs')
+test.subdir(['docs','sub'])
+
+test.write('SConstruct', """\
+env = Environment(TOOLS = ['tex', 'pdftex'])
+
+env.VariantDir('build', 'docs',duplicate=0)
+env.SConscript('build/SConscript', exports = ['env'])
+""")
+
+test.write(['docs','SConscript'], """\
+Import('env')
+
+env.PDF( 'sub/x.tex' )
+env.DVI( 'sub/x.tex' )
+""")
+
+test.write(['docs','sub', 'x.tex'],
+r"""\documentclass{article}
+\usepackage{makeidx}
+\makeindex
+\begin{document}
+Hi there.
+\index{info}
+\input{y}
+\printindex{}
+\end{document}
+""")
+
+test.write(['docs','sub', 'y.tex'], """\
+Sub-document 1
+""")
+
+#test.run(arguments = '.')
+test.run(arguments = '.', stderr=None, stdout=None)
+
+test.must_exist(['build', 'sub', 'x.aux'])
+test.must_exist(['build', 'sub', 'x.dvi'])
+test.must_exist(['build', 'sub', 'x.idx'])
+test.must_exist(['build', 'sub', 'x.ilg'])
+test.must_exist(['build', 'sub', 'x.ind'])
+test.must_exist(['build', 'sub', 'x.log'])
+test.must_exist(['build', 'sub', 'x.pdf'])
+
+test.must_not_exist('x.aux')
+test.must_not_exist('x.dvi')
+test.must_not_exist('x.idx')
+test.must_not_exist('x.ilg')
+test.must_not_exist('x.ind')
+test.must_not_exist('x.log')
+test.must_not_exist('x.pdf')
+
+test.must_not_exist(['docs', 'x.aux'])
+test.must_not_exist(['docs', 'x.dvi'])
+test.must_not_exist(['docs', 'x.idx'])
+test.must_not_exist(['docs', 'x.ilg'])
+test.must_not_exist(['docs', 'x.ind'])
+test.must_not_exist(['docs', 'x.log'])
+test.must_not_exist(['docs', 'x.pdf'])
+
+test.must_not_exist(['docs', 'sub', 'x.aux'])
+test.must_not_exist(['docs', 'sub', 'x.dvi'])
+test.must_not_exist(['docs', 'sub', 'x.idx'])
+test.must_not_exist(['docs', 'sub', 'x.ilg'])
+test.must_not_exist(['docs', 'sub', 'x.ind'])
+test.must_not_exist(['docs', 'sub', 'x.log'])
+test.must_not_exist(['docs', 'sub', 'x.pdf'])
+
+test.up_to_date(arguments = '.', stderr=None, stdout=None)
+
+test.write(['docs','sub', 'y.tex'], """\
+Sub-document 2
+""")
+
+test.not_up_to_date(arguments = '.')
+#test.up_to_date(arguments = '.', stderr=None, stdout=None)
+
+test.pass_test()
diff --git a/test/TEX/variant_dir_dup0.py b/test/TEX/variant_dir_dup0.py
index c37a13b..b03e89c 100644
--- a/test/TEX/variant_dir_dup0.py
+++ b/test/TEX/variant_dir_dup0.py
@@ -28,6 +28,9 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
Test creation of a fully-featured TeX document (with bibliography
and index) in a variant_dir.
+Also test that the target can be named differently than what
+Latex produces by default.
+
Test courtesy Rob Managan.
"""
@@ -62,7 +65,9 @@ test.write(['docs', 'SConscript'], """\
Import('env')
test_dvi = env.DVI(source='test.tex')
-testpdf = env.PDF(source=test_dvi)
+test2_dvi = env.DVI(target='result',source='test2.tex')
+testpdf = env.PDF(target='pdfoutput',source=test_dvi)
+test2pdf = env.PDF(target='pdfoutput.xyz',source=test2_dvi)
""")
@@ -228,6 +233,63 @@ All done now.
\end{document}
""")
+test.write(['docs', 'test2.tex'],
+r"""\documentclass{report}
+
+\usepackage{graphicx}
+\usepackage{epsfig,color} % for .tex version of figures if we go that way
+
+\usepackage{makeidx}
+\makeindex
+
+\begin{document}
+
+\title{Report Title}
+
+\author{A. N. Author}
+
+\maketitle
+
+\begin{abstract}
+there is no abstract
+\end{abstract}
+
+\tableofcontents
+\listoffigures
+
+\chapter{Introduction}
+
+The introduction is short.
+
+\index{Acknowledgements}
+
+\section{Acknowledgements}
+
+The Acknowledgements are show as well \cite{AnAuthor:2006fk}.
+
+\index{Getting the Report}
+
+To get a hard copy of this report call me.
+
+\begin{figure}[htbp]
+\begin{center}
+\input{Fig1.tex} % testing figure variant that uses TeX labeling
+\caption{Zone and Node indexing}
+\label{fig1}
+\end{center}
+\end{figure}
+
+All done now.
+
+\bibliographystyle{unsrt}
+\bibliography{test}
+\newpage
+
+\printindex
+
+\end{document}
+""")
+
# makeindex will write status messages to stderr (grrr...), so ignore it.
test.run(arguments = '.', stderr=None)
@@ -245,8 +307,19 @@ files = [
'test.ind',
'test.lof',
'test.log',
- 'test.pdf',
'test.toc',
+ 'test2.aux',
+ 'test2.bbl',
+ 'test2.blg',
+ 'test2.idx',
+ 'test2.ilg',
+ 'test2.ind',
+ 'test2.lof',
+ 'test2.log',
+ 'test2.toc',
+ 'result.dvi',
+ 'pdfoutput.pdf',
+ 'pdfoutput.xyz'
]
for f in files: