LatexRunner.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. /* eslint-disable
  2. camelcase,
  3. handle-callback-err,
  4. no-return-assign,
  5. no-unused-vars,
  6. */
  7. // TODO: This file was created by bulk-decaffeinate.
  8. // Fix any style issues and re-enable lint.
  9. /*
  10. * decaffeinate suggestions:
  11. * DS102: Remove unnecessary code created because of implicit returns
  12. * DS103: Rewrite code to no longer use __guard__
  13. * DS207: Consider shorter variations of null checks
  14. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  15. */
  16. let LatexRunner
  17. const Path = require('path')
  18. const Settings = require('settings-sharelatex')
  19. const logger = require('logger-sharelatex')
  20. const Metrics = require('./Metrics')
  21. const CommandRunner = require('./CommandRunner')
  22. const fs = require('fs')
  23. const ProcessTable = {} // table of currently running jobs (pids or docker container names)
  24. module.exports = LatexRunner = {
  25. runLatex(project_id, options, callback) {
  26. let command
  27. if (callback == null) {
  28. callback = function (error) {}
  29. }
  30. let {
  31. directory,
  32. mainFile,
  33. compiler,
  34. timeout,
  35. image,
  36. environment,
  37. flags,
  38. compileGroup
  39. } = options
  40. if (!compiler) {
  41. compiler = 'pdflatex'
  42. }
  43. if (!timeout) {
  44. timeout = 60000
  45. } // milliseconds
  46. logger.log(
  47. {
  48. directory,
  49. compiler,
  50. timeout,
  51. mainFile,
  52. environment,
  53. flags,
  54. compileGroup
  55. },
  56. 'starting compile'
  57. )
  58. // We want to run latexmk on the tex file which we will automatically
  59. // generate from the Rtex/Rmd/md file.
  60. mainFile = mainFile.replace(/\.(Rtex|md|Rmd)$/, '.tex')
  61. if (compiler === 'pdflatex') {
  62. command = LatexRunner._pdflatexCommand(mainFile, flags)
  63. } else if (compiler === 'latex') {
  64. command = LatexRunner._latexCommand(mainFile, flags)
  65. } else if (compiler === 'xelatex') {
  66. command = LatexRunner._xelatexCommand(mainFile, flags)
  67. } else if (compiler === 'lualatex') {
  68. command = LatexRunner._lualatexCommand(mainFile, flags)
  69. } else {
  70. return callback(new Error(`unknown compiler: ${compiler}`))
  71. }
  72. if (Settings.clsi != null ? Settings.clsi.strace : undefined) {
  73. command = ['strace', '-o', 'strace', '-ff'].concat(command)
  74. }
  75. const id = `${project_id}` // record running project under this id
  76. return (ProcessTable[id] = CommandRunner.run(
  77. project_id,
  78. command,
  79. directory,
  80. image,
  81. timeout,
  82. environment,
  83. compileGroup,
  84. function (error, output) {
  85. delete ProcessTable[id]
  86. if (error != null) {
  87. return callback(error)
  88. }
  89. const runs =
  90. __guard__(
  91. __guard__(output != null ? output.stderr : undefined, (x1) =>
  92. x1.match(/^Run number \d+ of .*latex/gm)
  93. ),
  94. (x) => x.length
  95. ) || 0
  96. const failed =
  97. __guard__(output != null ? output.stdout : undefined, (x2) =>
  98. x2.match(/^Latexmk: Errors/m)
  99. ) != null
  100. ? 1
  101. : 0
  102. // counters from latexmk output
  103. const stats = {}
  104. stats['latexmk-errors'] = failed
  105. stats['latex-runs'] = runs
  106. stats['latex-runs-with-errors'] = failed ? runs : 0
  107. stats[`latex-runs-${runs}`] = 1
  108. stats[`latex-runs-with-errors-${runs}`] = failed ? 1 : 0
  109. // timing information from /usr/bin/time
  110. const timings = {}
  111. const stderr = output != null ? output.stderr : undefined
  112. timings['cpu-percent'] =
  113. __guard__(
  114. stderr != null
  115. ? stderr.match(/Percent of CPU this job got: (\d+)/m)
  116. : undefined,
  117. (x3) => x3[1]
  118. ) || 0
  119. timings['cpu-time'] =
  120. __guard__(
  121. stderr != null
  122. ? stderr.match(/User time.*: (\d+.\d+)/m)
  123. : undefined,
  124. (x4) => x4[1]
  125. ) || 0
  126. timings['sys-time'] =
  127. __guard__(
  128. stderr != null
  129. ? stderr.match(/System time.*: (\d+.\d+)/m)
  130. : undefined,
  131. (x5) => x5[1]
  132. ) || 0
  133. // record output files
  134. LatexRunner.writeLogOutput(project_id, directory, output, () => {
  135. return callback(error, output, stats, timings)
  136. })
  137. }
  138. ))
  139. },
  140. writeLogOutput(project_id, directory, output, callback) {
  141. if (!output) {
  142. return callback()
  143. }
  144. // internal method for writing non-empty log files
  145. function _writeFile(file, content, cb) {
  146. if (content && content.length > 0) {
  147. fs.writeFile(file, content, (err) => {
  148. if (err) {
  149. logger.error({ project_id, file }, 'error writing log file') // don't fail on error
  150. }
  151. cb()
  152. })
  153. } else {
  154. cb()
  155. }
  156. }
  157. // write stdout and stderr, ignoring errors
  158. _writeFile(Path.join(directory, 'output.stdout'), output.stdout, () => {
  159. _writeFile(Path.join(directory, 'output.stderr'), output.stderr, () => {
  160. callback()
  161. })
  162. })
  163. },
  164. killLatex(project_id, callback) {
  165. if (callback == null) {
  166. callback = function (error) {}
  167. }
  168. const id = `${project_id}`
  169. logger.log({ id }, 'killing running compile')
  170. if (ProcessTable[id] == null) {
  171. logger.warn({ id }, 'no such project to kill')
  172. return callback(null)
  173. } else {
  174. return CommandRunner.kill(ProcessTable[id], callback)
  175. }
  176. },
  177. _latexmkBaseCommand(flags) {
  178. let args = [
  179. 'latexmk',
  180. '-cd',
  181. '-f',
  182. '-jobname=output',
  183. '-auxdir=$COMPILE_DIR',
  184. '-outdir=$COMPILE_DIR',
  185. '-synctex=1',
  186. '-interaction=batchmode'
  187. ]
  188. if (flags) {
  189. args = args.concat(flags)
  190. }
  191. return (
  192. __guard__(
  193. Settings != null ? Settings.clsi : undefined,
  194. (x) => x.latexmkCommandPrefix
  195. ) || []
  196. ).concat(args)
  197. },
  198. _pdflatexCommand(mainFile, flags) {
  199. return LatexRunner._latexmkBaseCommand(flags).concat([
  200. '-pdf',
  201. Path.join('$COMPILE_DIR', mainFile)
  202. ])
  203. },
  204. _latexCommand(mainFile, flags) {
  205. return LatexRunner._latexmkBaseCommand(flags).concat([
  206. '-pdfdvi',
  207. Path.join('$COMPILE_DIR', mainFile)
  208. ])
  209. },
  210. _xelatexCommand(mainFile, flags) {
  211. return LatexRunner._latexmkBaseCommand(flags).concat([
  212. '-xelatex',
  213. Path.join('$COMPILE_DIR', mainFile)
  214. ])
  215. },
  216. _lualatexCommand(mainFile, flags) {
  217. return LatexRunner._latexmkBaseCommand(flags).concat([
  218. '-lualatex',
  219. Path.join('$COMPILE_DIR', mainFile)
  220. ])
  221. }
  222. }
  223. function __guard__(value, transform) {
  224. return typeof value !== 'undefined' && value !== null
  225. ? transform(value)
  226. : undefined
  227. }