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