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