Commits

Anonymous committed 7900432

Correct $SOURCES on TeX command lines. (Sanjoy Mahajan) Add scanning of LaTeX files for implicit dependencies. (August Hörandl) Add support for generating indices from .idx files. (August Hörandl)

  • Participants
  • Parent commits 245e829

Comments (0)

Files changed (16)

File doc/man/scons.1

 .B DVI
 builder method will also examine the contents
 of the
-.B .aux file
-and invoke the $BIBTEX command line
+.B .aux
+file and invoke the $BIBTEX command line
 if the string
 .B bibdata
 is found,
+start $MAKEINDEX to generate an index if a
+.B .ind
+file is found
 and will examine the contents
 .B .log
 file and re-run the $LATEXCOM command
 .IP LATEXFLAGS
 General options passed to the LaTeX structured formatter and typesetter.
 
+.IP LATEXSUFFIXES
+The list of suffixes of files that will be scanned
+for LaTeX implicit dependencies
+(\\include or \\import files).
+The default list is:
+
+.ES
+[".tex", ".ltx", ".latex"]
+.EE
+
 .IP LDMODULE
 The linker for building loadable modules.
 By default, this is the same as $SHLINK.
 a file is passed through the M4 macro preprocessor.
 If this is not set, then $M4COM (the command line) is displayed.
 
+.IP MAKEINDEX
+The makeindex generator for the TeX formatter and typesetter and the
+LaTeX structured formatter and typesetter.
+
+.IP MAKEINDEXCOM
+The command line used to call the makeindex generator for the
+TeX formatter and typesetter and the LaTeX structured formatter and
+typesetter.
+
+.IP MAKEINDEXCOMSTR
+The string displayed when calling the makeindex generator for the
+TeX formatter and typesetter
+and the LaTeX structured formatter and typesetter.
+If this is not set, then $MAKEINDEXCOM (the command line) is displayed.
+
+.IP MAKEINDEXFLAGS
+General options passed to the makeindex generator for the TeX formatter
+and typesetter and the LaTeX structured formatter and typesetter.
+
 .IP MAXLINELENGTH
 The maximum number of characters allowed on an external command line.
 On Win32 systems,
 .IP TEXFLAGS
 General options passed to the TeX formatter and typesetter.
 
+.IP TEXINPUTS
+List of directories that the LaTeX programm will search
+for include directories.
+The LaTeX implicit dependency scanner will search these
+directories for \include and \import files.
+
 .IP TOOLS
 A list of the names of the Tool specifications
 that are part of this construction environment.

File src/CHANGES.txt

   - Remove unneceesary (and incorrect) SCons.Util strings on some function
     calls in SCons.Util.
 
+  From August Hörandl:
+
+  - Add a scanner for \include and \import files, with support for
+    searching a directory list in $TEXINPUTS (imported from the external
+    environment).
+
+  - Support $MAKEINDEX, $MAKEINDEXCOM, $MAKEINDEXCOMSTR and
+    $MAKEINDEXFLAGS for generating indices from .idx files.
+
   From Stephen Kennedy:
 
   - Speed up writing the .sconsign file at the end of a run by only
 
   From Sanjoy Mahajan:
 
+  - Correct TeX-related command lines to just $SOURCE, not $SOURCES
+
   - Fix a bad use of Copy() in an example in the man page, and a
     bad regular expression example in the man page and User's Guide.
 

File src/engine/MANIFEST.in

 SCons/Scanner/Dir.py
 SCons/Scanner/Fortran.py
 SCons/Scanner/IDL.py
+SCons/Scanner/LaTeX.py
 SCons/Scanner/Prog.py
 SCons/SConf.py
 SCons/SConsign.py

File src/engine/SCons/Defaults.py

 # transition period.
 CScan = SCons.Tool.CScanner
 DScan = SCons.Tool.DScanner
+LaTeXScan = SCons.Tool.LaTeXScanner
 ObjSourceScan = SCons.Tool.SourceFileScanner
 ProgScan = SCons.Tool.ProgramScanner
 
 def DVI():
     """Common function to generate a DVI file Builder."""
     return SCons.Builder.Builder(action = {},
+                                 source_scanner = LaTeXScan,
                                  # The suffix is not configurable via a
                                  # construction variable like $DVISUFFIX
                                  # because the output file name is
 def PDF():
     """A function for generating the PDF Builder."""
     return SCons.Builder.Builder(action = { },
+                                 source_scanner = LaTeXScan,
                                  prefix = '$PDFPREFIX',
                                  suffix = '$PDFSUFFIX')
 
     'CPPSUFFIXES'   : SCons.Tool.CSuffixes,
     'DSUFFIXES'     : SCons.Tool.DSuffixes,
     'IDLSUFFIXES'   : SCons.Tool.IDLSuffixes,
+    'LATEXSUFFIXES' : SCons.Tool.LaTeXSuffixes,
     'PDFPREFIX'     : '',
     'PDFSUFFIX'     : '.pdf',
     'PSPREFIX'      : '',

File src/engine/SCons/Defaults.xml

 builder method will also examine the contents
 of the
 <filename>.aux</filename>
-file
-and invoke the &cv-BIBTEX; command line
+file and invoke the &cv-BIBTEX; command line
 if the string
 <literal>bibdata</literal>
 is found,
+start &cv-MAKEINDEX; to generate an index if a
+<filename>.ind</filename>
+file is found
 and will examine the contents
 <filename>.log</filename>
 file and re-run the &cv-LATEXCOM; command
 </summary>
 </cvar>
 
+<cvar name="LATEXSUFFIXES">
+<summary>
+The list of suffixes of files that will be scanned
+for LaTeX implicit dependencies
+(<literal>\include</literal> or <literal>\import</literal> files).
+The default list is:
+
+<example>
+[".tex", ".ltx", ".latex"]
+</example>
+</summary>
+</cvar>
+
 <cvar name="_LIBDIRFLAGS">
 <summary>
 An automatically-generated construction variable

File src/engine/SCons/Scanner/LaTeX.py

+"""SCons.Scanner.LaTeX
+
+This module implements the dependency scanner for LaTeX code. 
+
+"""
+
+#
+# Copyright (c) 2005 The SCons Foundation
+#
+# 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__ = ""
+
+
+import SCons.Scanner
+
+def LaTeXScanner(fs = SCons.Node.FS.default_fs):
+    """Return a prototype Scanner instance for scanning LaTeX source files"""
+    ds = LaTeX(name = "LaTeXScanner",
+           suffixes =  '$LATEXSUFFIXES',
+           path_variable = 'TEXINPUTS',
+           regex = '\\\\(include|input){([^}]*)}',
+           recursive = 0)
+    return ds
+
+class LaTeX(SCons.Scanner.Classic):
+    def find_include(self, include, source_dir, path):
+        if callable(path): path=path()
+        # find (2nd result reg expr) + extension
+        # print 'looking for latex includes: ' + include[1]
+        i = SCons.Node.FS.find_file(include[1] + '.tex',
+                                    (source_dir,) + path)
+        return i, include

File src/engine/SCons/Scanner/LaTeXTests.py

+#
+# Copyright (c) 2005 The SCons Foundation
+#
+# 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__ = ""
+
+import os.path
+import string
+import sys
+import types
+import unittest
+import UserDict
+
+import TestCmd
+import SCons.Node.FS
+import SCons.Scanner.LaTeX
+
+test = TestCmd.TestCmd(workdir = '')
+
+test.write('test1.latex',"""
+\include{inc1}
+\input{inc2}
+""")
+test.write('test2.latex',"""
+\include{inc1}
+\include{inc3}
+""")
+
+test.subdir('subdir')
+
+test.write('inc1.tex',"\n")
+test.write('inc2.tex',"\n")
+test.write([ 'subdir', 'inc3.tex'], "\n")
+
+# define some helpers:
+#   copied from CTest.py
+class DummyEnvironment(UserDict.UserDict):
+    def __init__(self, **kw):
+        UserDict.UserDict.__init__(self)
+        self.data.update(kw)
+        self.fs = SCons.Node.FS.FS(test.workpath(''))
+
+    def Dictionary(self, *args):
+        return self.data
+
+    def subst(self, strSubst):
+        if strSubst[0] == '$':
+            return self.data[strSubst[1:]]
+        return strSubst
+
+    def subst_list(self, strSubst):
+        if strSubst[0] == '$':
+            return [self.data[strSubst[1:]]]
+        return [[strSubst]]
+
+    def subst_path(self, path, target=None, source=None):
+        if type(path) != type([]):
+            path = [path]
+        return map(self.subst, path)
+
+    def get_calculator(self):
+        return None
+
+    def get_factory(self, factory):
+        return factory or self.fs.File
+
+    def Dir(self, filename):
+        return self.fs.Dir(filename)
+
+    def File(self, filename):
+        return self.fs.File(filename)
+
+if os.path.normcase('foo') == os.path.normcase('FOO'):
+    my_normpath = os.path.normcase
+else:
+    my_normpath = os.path.normpath
+
+def deps_match(self, deps, headers):
+    global my_normpath
+    scanned = map(my_normpath, map(str, deps))
+    expect = map(my_normpath, headers)
+    self.failUnless(scanned == expect, "expect %s != scanned %s" % (expect, scanned))
+
+
+class LaTeXScannerTestCase1(unittest.TestCase):
+    def runTest(self):
+        env = DummyEnvironment()
+        s = SCons.Scanner.LaTeX.LaTeXScanner()
+        path = s.path(env)
+        deps = s(env.File('test1.latex'), env, path)
+        headers = ['inc1.tex', 'inc2.tex']
+        deps_match(self, deps, headers)
+
+class LaTeXScannerTestCase2(unittest.TestCase):
+     def runTest(self):
+         env = DummyEnvironment(TEXINPUTS=[test.workpath("subdir")])
+         s = SCons.Scanner.LaTeX.LaTeXScanner()
+         path = s.path(env)
+         deps = s(env.File('test2.latex'), env, path)
+         headers = ['inc1.tex', 'subdir/inc3.tex']
+         deps_match(self, deps, headers)
+
+
+def suite():
+    suite = unittest.TestSuite()
+    suite.addTest(LaTeXScannerTestCase1())
+    suite.addTest(LaTeXScannerTestCase2())
+    return suite
+
+if __name__ == "__main__":
+    runner = unittest.TextTestRunner()
+    result = runner.run(suite())
+    if not result.wasSuccessful():
+        sys.exit(1)
+

File src/engine/SCons/Tool/__init__.py

 import SCons.Scanner
 import SCons.Scanner.C
 import SCons.Scanner.D
+import SCons.Scanner.LaTeX
 import SCons.Scanner.Prog
 
 CScanner = SCons.Scanner.C.CScanner()
 DScanner = SCons.Scanner.D.DScanner()
+LaTeXScanner = SCons.Scanner.LaTeX.LaTeXScanner()
 ProgramScanner = SCons.Scanner.Prog.ProgramScanner()
 SourceFileScanner = SCons.Scanner.Scanner({}, name='SourceFileScanner')
 
 
 IDLSuffixes = [".idl", ".IDL"]
 
+LaTeXSuffixes = [".tex", ".ltx", ".latex"]
+
 for suffix in CSuffixes:
     SourceFileScanner.add_scanner(suffix, CScanner)
 
 for suffix in DSuffixes:
     SourceFileScanner.add_scanner(suffix, DScanner)
 
+for suffix in LaTeXSuffixes:
+     SourceFileScanner.add_scanner(suffix, LaTeXScanner)
+
 class Tool:
     def __init__(self, name, toolpath=[], **kw):
         self.name = name

File src/engine/SCons/Tool/dvips.py

     
     env['DVIPS']      = 'dvips'
     env['DVIPSFLAGS'] = SCons.Util.CLVar('')
-    env['PSCOM']      = '$DVIPS $DVIPSFLAGS -o $TARGET $SOURCES'
+    env['PSCOM']      = '$DVIPS $DVIPSFLAGS -o $TARGET $SOURCE'
 
 def exists(env):
     return env.Detect('dvips')

File src/engine/SCons/Tool/latex.py

 
 import SCons.Action
 import SCons.Defaults
+import SCons.Scanner.LaTeX
 import SCons.Util
+import SCons.Tool
 
 LaTeXAction = SCons.Action.Action('$LATEXCOM', '$LATEXCOMSTR')
 
         bld = SCons.Defaults.DVI()
         env['BUILDERS']['DVI'] = bld
         
-    bld.add_action('.ltx', LaTeXAction)
-    bld.add_action('.latex', LaTeXAction)
+    for suffix in SCons.Tool.LaTeXSuffixes:
+        bld.add_action(suffix, LaTeXAction)
 
     env['LATEX']      = 'latex'
     env['LATEXFLAGS'] = SCons.Util.CLVar('')

File src/engine/SCons/Tool/latex.xml

 General options passed to the LaTeX structured formatter and typesetter.
 </summary>
 </cvar>
+
+<cvar name="TEXINPUTS">
+<summary>
+List of directories that the LaTeX programm will search
+for include directories.
+The LaTeX implicit dependency scanner will search these
+directories for \include and \import files.
+</summary>
+</cvar>
+

File src/engine/SCons/Tool/pdflatex.py

 
     env['PDFLATEX']      = 'pdflatex'
     env['PDFLATEXFLAGS'] = SCons.Util.CLVar('')
-    env['PDFLATEXCOM']   = '$PDFLATEX $PDFLATEXFLAGS $SOURCES $TARGET'
+    env['PDFLATEXCOM']   = '$PDFLATEX $PDFLATEXFLAGS $SOURCE'
 
 def exists(env):
     return env.Detect('pdflatex')

File src/engine/SCons/Tool/pdftex.py

 
     env['PDFTEX']      = 'pdftex'
     env['PDFTEXFLAGS'] = SCons.Util.CLVar('')
-    env['PDFTEXCOM']   = '$PDFTEX $PDFTEXFLAGS $SOURCES $TARGET'
+    env['PDFTEXCOM']   = '$PDFTEX $PDFTEXFLAGS $SOURCE'
 
 def exists(env):
     return env.Detect('pdftex')

File src/engine/SCons/Tool/tex.py

 # Define an action to run BibTeX on a file.
 BibTeXAction = SCons.Action.Action("$BIBTEXCOM", "$BIBTEXCOMSTR")
 
+# Define an action to run MakeIndex on a file.
+MakeIndexAction = SCons.Action.Action("$MAKEINDEXCOM", "$MAKEINDEXOMSTR")
+
 def LaTeXAuxAction(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."""
     if string.find(content, "bibdata") != -1:
         bibfile = env.fs.File(basename)
         BibTeXAction(None,bibfile,env)
+    # Now if makeindex will need to be run.
+    idxfilename = basename + ".idx"
+    if os.path.exists(idxfilename):
+        idxfile = env.fs.File(basename)
+        # TODO: if ( idxfile has changed) ...
+        MakeIndexAction(None,idxfile,env)
+        LaTeXAction(target,source,env)
+        
     # Now check if latex needs to be run yet again.
     for trial in range(3):
         content = open(basename + ".log","rb").read()
 
     env['TEX']      = 'tex'
     env['TEXFLAGS'] = SCons.Util.CLVar('')
-    env['TEXCOM']   = '$TEX $TEXFLAGS $SOURCES'
+    env['TEXCOM']   = '$TEX $TEXFLAGS $SOURCE'
 
     # Duplicate from latex.py.  If latex.py goes away, then this is still OK.
     env['LATEX']      = 'latex'
     env['LATEXFLAGS'] = SCons.Util.CLVar('')
-    env['LATEXCOM']   = '$LATEX $LATEXFLAGS $SOURCES'
+    env['LATEXCOM']   = '$LATEX $LATEXFLAGS $SOURCE'
 
     env['BIBTEX']      = 'bibtex'
     env['BIBTEXFLAGS'] = SCons.Util.CLVar('')
-    env['BIBTEXCOM']   = '$BIBTEX $BIBTEXFLAGS $SOURCES'
+    env['BIBTEXCOM']   = '$BIBTEX $BIBTEXFLAGS $SOURCE'
 
+    env['MAKEINDEX']      = 'makeindex'
+    env['MAKEINDEXFLAGS'] = SCons.Util.CLVar('')
+    env['MAKEINDEXCOM']   = '$MAKEINDEX $MAKEINDEXFLAGS $SOURCES'
 
+    
 def exists(env):
     return env.Detect('tex')

File src/engine/SCons/Tool/tex.xml

 </summary>
 </cvar>
 
+<cvar name="MAKEINDEX">
+<summary>
+The makeindex generator for the TeX formatter and typesetter and the
+LaTeX structured formatter and typesetter.
+</summary>
+</cvar>
+
+<cvar name="MAKEINDEXCOM">
+<summary>
+The command line used to call the makeindex generator for the
+TeX formatter and typesetter and the LaTeX structured formatter and
+typesetter.
+</summary>
+</cvar>
+
+<cvar name="MAKEINDEXCOMSTR">
+<summary>
+The string displayed when calling the makeindex generator for the
+TeX formatter and typesetter
+and the LaTeX structured formatter and typesetter.
+If this is not set, then &cv-MAKEINDEXCOM; (the command line) is displayed.
+</summary>
+</cvar>
+
+<cvar name="MAKEINDEXFLAGS">
+<summary>
+General options passed to the makeindex generator for the TeX formatter
+and typesetter and the LaTeX structured formatter and typesetter.
+</summary>
+</cvar>
+
 <cvar name="TEX">
 <summary>
 The TeX formatter and typesetter.

File test/TEX/LATEX.py

 
     test.write('SConstruct', """
 import os
-ENV = { 'PATH' : os.environ['PATH'] }
+ENV = { 'PATH' : os.environ['PATH'],
+        'TEXINPUTS' : [ 'subdir', os.environ.get('TEXINPUTS', '') ] }
 foo = Environment(ENV = ENV)
 latex = foo.Dictionary('LATEX')
-bar = Environment(ENV = ENV, LATEX = r'%s wrapper.py ' + latex)
+makeindex = foo.Dictionary('MAKEINDEX')
+bar = Environment(ENV = ENV,
+                  LATEX = r'%s wrapper.py ' + latex,
+                  MAKEINDEX =  r' wrapper.py ' + makeindex)
 foo.DVI(target = 'foo.dvi', source = 'foo.ltx')
 bar.DVI(target = 'bar', source = 'bar.latex')
+
+bar.DVI(target = 'makeindex', source = 'makeindex.tex')
+foo.DVI(target = 'latexi', source = 'latexi.tex')
 """ % python)
 
     latex = r"""
 \end{document}
 """
 
+    makeindex =  r"""
+\documentclass{letter}
+\usepackage{makeidx}
+\makeindex
+\begin{document}
+\index{info}
+This is the %s LaTeX file.
+\printindex{}
+\end{document}
+"""
+
+    latex1 = r"""
+\documentclass{letter}
+\usepackage{makeidx}
+\makeindex
+\begin{document}
+\index{info}
+This is the %s LaTeX file.
+
+It has an Index and includes another file.
+\include{latexincludefile}
+\end{document}
+"""
+
+    latex2 = r"""
+\index{include}
+This is the include file.
+\printindex{}
+"""
+
     test.write('foo.ltx', latex % 'foo.ltx')
 
     test.write('bar.latex', latex % 'bar.latex')
 
+    test.write('makeindex.tex',  makeindex % 'makeindex.tex');
+    test.write('makeindex.idx',  '');
+
+    test.subdir('subdir')
+    test.write('latexi.tex',  latex1 % 'latexi.tex');
+    test.write([ 'subdir', 'latexincludefile.tex'], latex2)
+
     test.run(arguments = 'foo.dvi', stderr = None)
-
     test.fail_test(os.path.exists(test.workpath('wrapper.out')))
-
     test.fail_test(not os.path.exists(test.workpath('foo.dvi')))
 
     test.run(arguments = 'bar.dvi', stderr = None)
+    test.fail_test(test.read('wrapper.out') != "wrapper.py\n")
+    test.fail_test(not os.path.exists(test.workpath('bar.dvi')))
 
+    test.run(arguments = 'makeindex.dvi', stderr = None)
     test.fail_test(test.read('wrapper.out') != "wrapper.py\n")
 
-    test.fail_test(not os.path.exists(test.workpath('bar.dvi')))
+    test.run(arguments = 'latexi.dvi', stderr = None)
+    test.fail_test(not os.path.exists(test.workpath('latexi.dvi')))
+    test.fail_test(not os.path.exists(test.workpath('latexi.ind')))
+
+
 
 test.pass_test()