PageRenderTime 64ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/Code/Tools/waf-1.7.13/waflib/Tools/tex.py

https://gitlab.com/dahbearz/CRYENGINE
Python | 431 lines | 421 code | 1 blank | 9 comment | 1 complexity | c3dd9990ff36224626e466064845e5f7 MD5 | raw file
  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. # Thomas Nagy, 2006-2010 (ita)
  4. """
  5. TeX/LaTeX/PDFLaTeX/XeLaTeX support
  6. Example::
  7. def configure(conf):
  8. conf.load('tex')
  9. if not conf.env.LATEX:
  10. conf.fatal('The program LaTex is required')
  11. def build(bld):
  12. bld(
  13. features = 'tex',
  14. type = 'latex', # pdflatex or xelatex
  15. source = 'document.ltx', # mandatory, the source
  16. outs = 'ps', # 'pdf' or 'ps pdf'
  17. deps = 'crossreferencing.lst', # to give dependencies directly
  18. prompt = 1, # 0 for the batch mode
  19. )
  20. To configure with a special program use::
  21. $ PDFLATEX=luatex waf configure
  22. """
  23. import os, re
  24. from waflib import Utils, Task, Errors, Logs
  25. from waflib.TaskGen import feature, before_method
  26. re_bibunit = re.compile(r'\\(?P<type>putbib)\[(?P<file>[^\[\]]*)\]',re.M)
  27. def bibunitscan(self):
  28. """
  29. Parse the inputs and try to find the *bibunit* dependencies
  30. :return: list of bibunit files
  31. :rtype: list of :py:class:`waflib.Node.Node`
  32. """
  33. node = self.inputs[0]
  34. nodes = []
  35. if not node: return nodes
  36. code = node.read()
  37. for match in re_bibunit.finditer(code):
  38. path = match.group('file')
  39. if path:
  40. for k in ['', '.bib']:
  41. # add another loop for the tex include paths?
  42. Logs.debug('tex: trying %s%s' % (path, k))
  43. fi = node.parent.find_resource(path + k)
  44. if fi:
  45. nodes.append(fi)
  46. # no break, people are crazy
  47. else:
  48. Logs.debug('tex: could not find %s' % path)
  49. Logs.debug("tex: found the following bibunit files: %s" % nodes)
  50. return nodes
  51. exts_deps_tex = ['', '.ltx', '.tex', '.bib', '.pdf', '.png', '.eps', '.ps']
  52. """List of typical file extensions included in latex files"""
  53. exts_tex = ['.ltx', '.tex']
  54. """List of typical file extensions that contain latex"""
  55. re_tex = re.compile(r'\\(?P<type>include|bibliography|putbib|includegraphics|input|import|bringin|lstinputlisting)(\[[^\[\]]*\])?{(?P<file>[^{}]*)}',re.M)
  56. """Regexp for expressions that may include latex files"""
  57. g_bibtex_re = re.compile('bibdata', re.M)
  58. """Regexp for bibtex files"""
  59. class tex(Task.Task):
  60. """
  61. Compile a tex/latex file.
  62. .. inheritance-diagram:: waflib.Tools.tex.latex waflib.Tools.tex.xelatex waflib.Tools.tex.pdflatex
  63. """
  64. bibtex_fun, _ = Task.compile_fun('${BIBTEX} ${BIBTEXFLAGS} ${SRCFILE}', shell=False)
  65. bibtex_fun.__doc__ = """
  66. Execute the program **bibtex**
  67. """
  68. makeindex_fun, _ = Task.compile_fun('${MAKEINDEX} ${MAKEINDEXFLAGS} ${SRCFILE}', shell=False)
  69. makeindex_fun.__doc__ = """
  70. Execute the program **makeindex**
  71. """
  72. def exec_command(self, cmd, **kw):
  73. """
  74. Override :py:meth:`waflib.Task.Task.exec_command` to execute the command without buffering (latex may prompt for inputs)
  75. :return: the return code
  76. :rtype: int
  77. """
  78. bld = self.generator.bld
  79. try:
  80. if not kw.get('cwd', None):
  81. kw['cwd'] = bld.cwd
  82. except AttributeError:
  83. bld.cwd = kw['cwd'] = bld.variant_dir
  84. return Utils.subprocess.Popen(cmd, **kw).wait()
  85. def scan_aux(self, node):
  86. """
  87. A recursive regex-based scanner that finds included auxiliary files.
  88. """
  89. nodes = [node]
  90. re_aux = re.compile(r'\\@input{(?P<file>[^{}]*)}', re.M)
  91. def parse_node(node):
  92. code = node.read()
  93. for match in re_aux.finditer(code):
  94. path = match.group('file')
  95. found = node.parent.find_or_declare(path)
  96. if found and found not in nodes:
  97. Logs.debug('tex: found aux node ' + found.abspath())
  98. nodes.append(found)
  99. parse_node(found)
  100. parse_node(node)
  101. return nodes
  102. def scan(self):
  103. """
  104. A recursive regex-based scanner that finds latex dependencies. It uses :py:attr:`waflib.Tools.tex.re_tex`
  105. Depending on your needs you might want:
  106. * to change re_tex::
  107. from waflib.Tools import tex
  108. tex.re_tex = myregex
  109. * or to change the method scan from the latex tasks::
  110. from waflib.Task import classes
  111. classes['latex'].scan = myscanfunction
  112. """
  113. node = self.inputs[0]
  114. nodes = []
  115. names = []
  116. seen = []
  117. if not node: return (nodes, names)
  118. def parse_node(node):
  119. if node in seen:
  120. return
  121. seen.append(node)
  122. code = node.read()
  123. global re_tex
  124. for match in re_tex.finditer(code):
  125. for path in match.group('file').split(','):
  126. if path:
  127. add_name = True
  128. found = None
  129. for k in exts_deps_tex:
  130. Logs.debug('tex: trying %s%s' % (path, k))
  131. found = node.parent.find_resource(path + k)
  132. for tsk in self.generator.tasks:
  133. if not found or found in tsk.outputs:
  134. break
  135. else:
  136. nodes.append(found)
  137. add_name = False
  138. for ext in exts_tex:
  139. if found.name.endswith(ext):
  140. parse_node(found)
  141. break
  142. # no break, people are crazy
  143. if add_name:
  144. names.append(path)
  145. parse_node(node)
  146. for x in nodes:
  147. x.parent.get_bld().mkdir()
  148. Logs.debug("tex: found the following : %s and names %s" % (nodes, names))
  149. return (nodes, names)
  150. def check_status(self, msg, retcode):
  151. """
  152. Check an exit status and raise an error with a particular message
  153. :param msg: message to display if the code is non-zero
  154. :type msg: string
  155. :param retcode: condition
  156. :type retcode: boolean
  157. """
  158. if retcode != 0:
  159. raise Errors.WafError("%r command exit status %r" % (msg, retcode))
  160. def bibfile(self):
  161. """
  162. Parse the *.aux* files to find bibfiles to process.
  163. If yes, execute :py:meth:`waflib.Tools.tex.tex.bibtex_fun`
  164. """
  165. for aux_node in self.aux_nodes:
  166. try:
  167. ct = aux_node.read()
  168. except (OSError, IOError):
  169. Logs.error('Error reading %s: %r' % aux_node.abspath())
  170. continue
  171. if g_bibtex_re.findall(ct):
  172. Logs.warn('calling bibtex')
  173. self.env.env = {}
  174. self.env.env.update(os.environ)
  175. self.env.env.update({'BIBINPUTS': self.TEXINPUTS, 'BSTINPUTS': self.TEXINPUTS})
  176. self.env.SRCFILE = aux_node.name[:-4]
  177. self.check_status('error when calling bibtex', self.bibtex_fun())
  178. def bibunits(self):
  179. """
  180. Parse the *.aux* file to find bibunit files. If there are bibunit files,
  181. execute :py:meth:`waflib.Tools.tex.tex.bibtex_fun`.
  182. """
  183. try:
  184. bibunits = bibunitscan(self)
  185. except OSError:
  186. Logs.error('error bibunitscan')
  187. else:
  188. if bibunits:
  189. fn = ['bu' + str(i) for i in xrange(1, len(bibunits) + 1)]
  190. if fn:
  191. Logs.warn('calling bibtex on bibunits')
  192. for f in fn:
  193. self.env.env = {'BIBINPUTS': self.TEXINPUTS, 'BSTINPUTS': self.TEXINPUTS}
  194. self.env.SRCFILE = f
  195. self.check_status('error when calling bibtex', self.bibtex_fun())
  196. def makeindex(self):
  197. """
  198. Look on the filesystem if there is a *.idx* file to process. If yes, execute
  199. :py:meth:`waflib.Tools.tex.tex.makeindex_fun`
  200. """
  201. try:
  202. idx_path = self.idx_node.abspath()
  203. os.stat(idx_path)
  204. except OSError:
  205. Logs.warn('index file %s absent, not calling makeindex' % idx_path)
  206. else:
  207. Logs.warn('calling makeindex')
  208. self.env.SRCFILE = self.idx_node.name
  209. self.env.env = {}
  210. self.check_status('error when calling makeindex %s' % idx_path, self.makeindex_fun())
  211. def bibtopic(self):
  212. """
  213. Additional .aux files from the bibtopic package
  214. """
  215. p = self.inputs[0].parent.get_bld()
  216. if os.path.exists(os.path.join(p.abspath(), 'btaux.aux')):
  217. self.aux_nodes += p.ant_glob('*[0-9].aux')
  218. def run(self):
  219. """
  220. Runs the TeX build process.
  221. It may require multiple passes, depending on the usage of cross-references,
  222. bibliographies, content susceptible of needing such passes.
  223. The appropriate TeX compiler is called until the *.aux* files stop changing.
  224. Makeindex and bibtex are called if necessary.
  225. """
  226. env = self.env
  227. if not env['PROMPT_LATEX']:
  228. env.append_value('LATEXFLAGS', '-interaction=batchmode')
  229. env.append_value('PDFLATEXFLAGS', '-interaction=batchmode')
  230. env.append_value('XELATEXFLAGS', '-interaction=batchmode')
  231. fun = self.texfun
  232. node = self.inputs[0]
  233. srcfile = node.abspath()
  234. texinputs = self.env.TEXINPUTS or ''
  235. self.TEXINPUTS = node.parent.get_bld().abspath() + os.pathsep + node.parent.get_src().abspath() + os.pathsep + texinputs + os.pathsep
  236. # important, set the cwd for everybody
  237. self.cwd = self.inputs[0].parent.get_bld().abspath()
  238. Logs.warn('first pass on %s' % self.__class__.__name__)
  239. self.env.env = {}
  240. self.env.env.update(os.environ)
  241. self.env.env.update({'TEXINPUTS': self.TEXINPUTS})
  242. self.env.SRCFILE = srcfile
  243. self.check_status('error when calling latex', fun())
  244. self.aux_nodes = self.scan_aux(node.change_ext('.aux'))
  245. self.idx_node = node.change_ext('.idx')
  246. self.bibtopic()
  247. self.bibfile()
  248. self.bibunits()
  249. self.makeindex()
  250. hash = ''
  251. for i in range(10):
  252. # prevent against infinite loops - one never knows
  253. # watch the contents of file.aux and stop if file.aux does not change anymore
  254. prev_hash = hash
  255. try:
  256. hashes = [Utils.h_file(x.abspath()) for x in self.aux_nodes]
  257. hash = Utils.h_list(hashes)
  258. except (OSError, IOError):
  259. Logs.error('could not read aux.h')
  260. pass
  261. if hash and hash == prev_hash:
  262. break
  263. # run the command
  264. Logs.warn('calling %s' % self.__class__.__name__)
  265. self.env.env = {}
  266. self.env.env.update(os.environ)
  267. self.env.env.update({'TEXINPUTS': self.TEXINPUTS})
  268. self.env.SRCFILE = srcfile
  269. self.check_status('error when calling %s' % self.__class__.__name__, fun())
  270. class latex(tex):
  271. texfun, vars = Task.compile_fun('${LATEX} ${LATEXFLAGS} ${SRCFILE}', shell=False)
  272. class pdflatex(tex):
  273. texfun, vars = Task.compile_fun('${PDFLATEX} ${PDFLATEXFLAGS} ${SRCFILE}', shell=False)
  274. class xelatex(tex):
  275. texfun, vars = Task.compile_fun('${XELATEX} ${XELATEXFLAGS} ${SRCFILE}', shell=False)
  276. class dvips(Task.Task):
  277. run_str = '${DVIPS} ${DVIPSFLAGS} ${SRC} -o ${TGT}'
  278. color = 'BLUE'
  279. after = ['latex', 'pdflatex', 'xelatex']
  280. class dvipdf(Task.Task):
  281. run_str = '${DVIPDF} ${DVIPDFFLAGS} ${SRC} ${TGT}'
  282. color = 'BLUE'
  283. after = ['latex', 'pdflatex', 'xelatex']
  284. class pdf2ps(Task.Task):
  285. run_str = '${PDF2PS} ${PDF2PSFLAGS} ${SRC} ${TGT}'
  286. color = 'BLUE'
  287. after = ['latex', 'pdflatex', 'xelatex']
  288. @feature('tex')
  289. @before_method('process_source')
  290. def apply_tex(self):
  291. """
  292. Create :py:class:`waflib.Tools.tex.tex` objects, and dvips/dvipdf/pdf2ps tasks if necessary (outs='ps', etc).
  293. """
  294. if not getattr(self, 'type', None) in ['latex', 'pdflatex', 'xelatex']:
  295. self.type = 'pdflatex'
  296. tree = self.bld
  297. outs = Utils.to_list(getattr(self, 'outs', []))
  298. # prompt for incomplete files (else the batchmode is used)
  299. self.env['PROMPT_LATEX'] = getattr(self, 'prompt', 1)
  300. deps_lst = []
  301. if getattr(self, 'deps', None):
  302. deps = self.to_list(self.deps)
  303. for filename in deps:
  304. n = self.path.find_resource(filename)
  305. if not n:
  306. self.bld.fatal('Could not find %r for %r' % (filename, self))
  307. if not n in deps_lst:
  308. deps_lst.append(n)
  309. for node in self.to_nodes(self.source):
  310. if self.type == 'latex':
  311. task = self.create_task('latex', node, node.change_ext('.dvi'))
  312. elif self.type == 'pdflatex':
  313. task = self.create_task('pdflatex', node, node.change_ext('.pdf'))
  314. elif self.type == 'xelatex':
  315. task = self.create_task('xelatex', node, node.change_ext('.pdf'))
  316. task.env = self.env
  317. # add the manual dependencies
  318. if deps_lst:
  319. try:
  320. lst = tree.node_deps[task.uid()]
  321. for n in deps_lst:
  322. if not n in lst:
  323. lst.append(n)
  324. except KeyError:
  325. tree.node_deps[task.uid()] = deps_lst
  326. v = dict(os.environ)
  327. p = node.parent.abspath() + os.pathsep + self.path.abspath() + os.pathsep + self.path.get_bld().abspath() + os.pathsep + v.get('TEXINPUTS', '') + os.pathsep
  328. v['TEXINPUTS'] = p
  329. if self.type == 'latex':
  330. if 'ps' in outs:
  331. tsk = self.create_task('dvips', task.outputs, node.change_ext('.ps'))
  332. tsk.env.env = dict(v)
  333. if 'pdf' in outs:
  334. tsk = self.create_task('dvipdf', task.outputs, node.change_ext('.pdf'))
  335. tsk.env.env = dict(v)
  336. elif self.type == 'pdflatex':
  337. if 'ps' in outs:
  338. self.create_task('pdf2ps', task.outputs, node.change_ext('.ps'))
  339. self.source = []
  340. def configure(self):
  341. """
  342. Try to find the programs tex, latex and others. Do not raise any error if they
  343. are not found.
  344. """
  345. v = self.env
  346. for p in 'tex latex pdflatex xelatex bibtex dvips dvipdf ps2pdf makeindex pdf2ps'.split():
  347. try:
  348. self.find_program(p, var=p.upper())
  349. except self.errors.ConfigurationError:
  350. pass
  351. v['DVIPSFLAGS'] = '-Ppdf'