Add flags option to request JSON

Adds a `flags` parameter to the request JSON, appearing under the `compile.options` key (alongside such stalwarts as `compiler`, `timeout`, etc.).

This is primarily to support `-file-line-error` as an option, but could have other uses as well.

`flags` should be an array of strings, or absent. If supplied, the listed arguments are added to the base latexmk command.
This commit is contained in:
Michael Mazour 2019-05-10 16:51:40 +01:00
parent 4ccaa3bf2f
commit 1ddf9283f2
6 changed files with 82 additions and 38 deletions

View file

@ -93,6 +93,7 @@ module.exports = CompileManager =
compiler: request.compiler compiler: request.compiler
timeout: request.timeout timeout: request.timeout
image: request.imageName image: request.imageName
flags: request.flags
environment: env environment: env
}, (error, output, stats, timings) -> }, (error, output, stats, timings) ->
# request was for validation only # request was for validation only
@ -130,7 +131,7 @@ module.exports = CompileManager =
return callback(error) if error? return callback(error) if error?
OutputCacheManager.saveOutputFiles outputFiles, compileDir, (error, newOutputFiles) -> OutputCacheManager.saveOutputFiles outputFiles, compileDir, (error, newOutputFiles) ->
callback null, newOutputFiles callback null, newOutputFiles
stopCompile: (project_id, user_id, callback = (error) ->) -> stopCompile: (project_id, user_id, callback = (error) ->) ->
compileName = getCompileName(project_id, user_id) compileName = getCompileName(project_id, user_id)
LatexRunner.killLatex compileName, callback LatexRunner.killLatex compileName, callback

View file

@ -8,27 +8,27 @@ ProcessTable = {} # table of currently running jobs (pids or docker container n
module.exports = LatexRunner = module.exports = LatexRunner =
runLatex: (project_id, options, callback = (error) ->) -> runLatex: (project_id, options, callback = (error) ->) ->
{directory, mainFile, compiler, timeout, image, environment} = options {directory, mainFile, compiler, timeout, image, environment, flags} = options
compiler ||= "pdflatex" compiler ||= "pdflatex"
timeout ||= 60000 # milliseconds timeout ||= 60000 # milliseconds
logger.log directory: directory, compiler: compiler, timeout: timeout, mainFile: mainFile, environment: environment, "starting compile" logger.log directory: directory, compiler: compiler, timeout: timeout, mainFile: mainFile, environment: environment, flags:flags, "starting compile"
# We want to run latexmk on the tex file which we will automatically # We want to run latexmk on the tex file which we will automatically
# generate from the Rtex/Rmd/md file. # generate from the Rtex/Rmd/md file.
mainFile = mainFile.replace(/\.(Rtex|md|Rmd)$/, ".tex") mainFile = mainFile.replace(/\.(Rtex|md|Rmd)$/, ".tex")
if compiler == "pdflatex" if compiler == "pdflatex"
command = LatexRunner._pdflatexCommand mainFile command = LatexRunner._pdflatexCommand mainFile, flags
else if compiler == "latex" else if compiler == "latex"
command = LatexRunner._latexCommand mainFile command = LatexRunner._latexCommand mainFile, flags
else if compiler == "xelatex" else if compiler == "xelatex"
command = LatexRunner._xelatexCommand mainFile command = LatexRunner._xelatexCommand mainFile, flags
else if compiler == "lualatex" else if compiler == "lualatex"
command = LatexRunner._lualatexCommand mainFile command = LatexRunner._lualatexCommand mainFile, flags
else else
return callback new Error("unknown compiler: #{compiler}") return callback new Error("unknown compiler: #{compiler}")
if Settings.clsi?.strace if Settings.clsi?.strace
command = ["strace", "-o", "strace", "-ff"].concat(command) command = ["strace", "-o", "strace", "-ff"].concat(command)
@ -63,31 +63,32 @@ module.exports = LatexRunner =
else else
CommandRunner.kill ProcessTable[id], callback CommandRunner.kill ProcessTable[id], callback
_latexmkBaseCommand: (Settings?.clsi?.latexmkCommandPrefix || []).concat([ _latexmkBaseCommand: (flags) ->
"latexmk", "-cd", "-f", "-jobname=output", "-auxdir=$COMPILE_DIR", "-outdir=$COMPILE_DIR", args = ["latexmk", "-cd", "-f", "-jobname=output", "-auxdir=$COMPILE_DIR", "-outdir=$COMPILE_DIR", "-synctex=1","-interaction=batchmode"]
"-synctex=1","-interaction=batchmode" if flags
]) args = args.concat(flags)
(Settings?.clsi?.latexmkCommandPrefix || []).concat(args)
_pdflatexCommand: (mainFile) -> _pdflatexCommand: (mainFile, flags) ->
LatexRunner._latexmkBaseCommand.concat [ LatexRunner._latexmkBaseCommand(flags).concat [
"-pdf", "-pdf",
Path.join("$COMPILE_DIR", mainFile) Path.join("$COMPILE_DIR", mainFile)
] ]
_latexCommand: (mainFile) -> _latexCommand: (mainFile, flags) ->
LatexRunner._latexmkBaseCommand.concat [ LatexRunner._latexmkBaseCommand(flags).concat [
"-pdfdvi", "-pdfdvi",
Path.join("$COMPILE_DIR", mainFile) Path.join("$COMPILE_DIR", mainFile)
] ]
_xelatexCommand: (mainFile) -> _xelatexCommand: (mainFile, flags) ->
LatexRunner._latexmkBaseCommand.concat [ LatexRunner._latexmkBaseCommand(flags).concat [
"-xelatex", "-xelatex",
Path.join("$COMPILE_DIR", mainFile) Path.join("$COMPILE_DIR", mainFile)
] ]
_lualatexCommand: (mainFile) -> _lualatexCommand: (mainFile, flags) ->
LatexRunner._latexmkBaseCommand.concat [ LatexRunner._latexmkBaseCommand(flags).concat [
"-lualatex", "-lualatex",
Path.join("$COMPILE_DIR", mainFile) Path.join("$COMPILE_DIR", mainFile)
] ]

View file

@ -12,7 +12,7 @@ module.exports = RequestParser =
compile = body.compile compile = body.compile
compile.options ||= {} compile.options ||= {}
try try
response.compiler = @_parseAttribute "compiler", response.compiler = @_parseAttribute "compiler",
compile.options.compiler, compile.options.compiler,
@ -33,6 +33,10 @@ module.exports = RequestParser =
response.check = @_parseAttribute "check", response.check = @_parseAttribute "check",
compile.options.check, compile.options.check,
type: "string" type: "string"
response.flags = @_parseAttribute "flags",
compile.options.flags,
default: [],
type: "object"
# The syncType specifies whether the request contains all # The syncType specifies whether the request contains all
# resources (full) or only those resources to be updated # resources (full) or only those resources to be updated
@ -68,7 +72,7 @@ module.exports = RequestParser =
originalRootResourcePath = rootResourcePath originalRootResourcePath = rootResourcePath
sanitizedRootResourcePath = RequestParser._sanitizePath(rootResourcePath) sanitizedRootResourcePath = RequestParser._sanitizePath(rootResourcePath)
response.rootResourcePath = RequestParser._checkPath(sanitizedRootResourcePath) response.rootResourcePath = RequestParser._checkPath(sanitizedRootResourcePath)
for resource in response.resources for resource in response.resources
if resource.path == originalRootResourcePath if resource.path == originalRootResourcePath
resource.path = sanitizedRootResourcePath resource.path = sanitizedRootResourcePath

View file

@ -13,7 +13,7 @@ describe "CompileManager", ->
"./ResourceWriter": @ResourceWriter = {} "./ResourceWriter": @ResourceWriter = {}
"./OutputFileFinder": @OutputFileFinder = {} "./OutputFileFinder": @OutputFileFinder = {}
"./OutputCacheManager": @OutputCacheManager = {} "./OutputCacheManager": @OutputCacheManager = {}
"settings-sharelatex": @Settings = "settings-sharelatex": @Settings =
path: path:
compilesDir: "/compiles/dir" compilesDir: "/compiles/dir"
synctexBaseDir: -> "/compile" synctexBaseDir: -> "/compile"
@ -108,6 +108,7 @@ describe "CompileManager", ->
compiler: @compiler = "pdflatex" compiler: @compiler = "pdflatex"
timeout: @timeout = 42000 timeout: @timeout = 42000
imageName: @image = "example.com/image" imageName: @image = "example.com/image"
flags: @flags = ["-file-line-error"]
@env = {} @env = {}
@Settings.compileDir = "compiles" @Settings.compileDir = "compiles"
@compileDir = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}" @compileDir = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}"
@ -117,7 +118,7 @@ describe "CompileManager", ->
@OutputCacheManager.saveOutputFiles = sinon.stub().callsArgWith(2, null, @build_files) @OutputCacheManager.saveOutputFiles = sinon.stub().callsArgWith(2, null, @build_files)
@DraftModeManager.injectDraftMode = sinon.stub().callsArg(1) @DraftModeManager.injectDraftMode = sinon.stub().callsArg(1)
@TikzManager.checkMainFile = sinon.stub().callsArg(3, false) @TikzManager.checkMainFile = sinon.stub().callsArg(3, false)
describe "normally", -> describe "normally", ->
beforeEach -> beforeEach ->
@CompileManager.doCompile @request, @callback @CompileManager.doCompile @request, @callback
@ -135,6 +136,7 @@ describe "CompileManager", ->
compiler: @compiler compiler: @compiler
timeout: @timeout timeout: @timeout
image: @image image: @image
flags: @flags
environment: @env environment: @env
}) })
.should.equal true .should.equal true
@ -146,15 +148,15 @@ describe "CompileManager", ->
it "should return the output files", -> it "should return the output files", ->
@callback.calledWith(null, @build_files).should.equal true @callback.calledWith(null, @build_files).should.equal true
it "should not inject draft mode by default", -> it "should not inject draft mode by default", ->
@DraftModeManager.injectDraftMode.called.should.equal false @DraftModeManager.injectDraftMode.called.should.equal false
describe "with draft mode", -> describe "with draft mode", ->
beforeEach -> beforeEach ->
@request.draft = true @request.draft = true
@CompileManager.doCompile @request, @callback @CompileManager.doCompile @request, @callback
it "should inject the draft mode header", -> it "should inject the draft mode header", ->
@DraftModeManager.injectDraftMode @DraftModeManager.injectDraftMode
.calledWith(@compileDir + "/" + @rootResourcePath) .calledWith(@compileDir + "/" + @rootResourcePath)
@ -173,6 +175,7 @@ describe "CompileManager", ->
compiler: @compiler compiler: @compiler
timeout: @timeout timeout: @timeout
image: @image image: @image
flags: @flags
environment: {'CHKTEX_OPTIONS': '-nall -e9 -e10 -w15 -w16', 'CHKTEX_EXIT_ON_ERROR':1, 'CHKTEX_ULIMIT_OPTIONS': '-t 5 -v 64000'} environment: {'CHKTEX_OPTIONS': '-nall -e9 -e10 -w15 -w16', 'CHKTEX_EXIT_ON_ERROR':1, 'CHKTEX_ULIMIT_OPTIONS': '-t 5 -v 64000'}
}) })
.should.equal true .should.equal true
@ -191,6 +194,7 @@ describe "CompileManager", ->
compiler: @compiler compiler: @compiler
timeout: @timeout timeout: @timeout
image: @image image: @image
flags: @flags
environment: @env environment: @env
}) })
.should.equal true .should.equal true
@ -297,7 +301,7 @@ describe "CompileManager", ->
@CommandRunner.run @CommandRunner.run
.calledWith( .calledWith(
"#{@project_id}-#{@user_id}", "#{@project_id}-#{@user_id}",
['/opt/synctex', "pdf", synctex_path, @page, @h, @v], ['/opt/synctex', "pdf", synctex_path, @page, @h, @v],
"#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}", "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}",
@Settings.clsi.docker.image, @Settings.clsi.docker.image,
60000, 60000,
@ -330,7 +334,7 @@ describe "CompileManager", ->
@directory = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}" @directory = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}"
@file_path = "$COMPILE_DIR/#{@file_name}" @file_path = "$COMPILE_DIR/#{@file_name}"
@command =[ "texcount", "-nocol", "-inc", @file_path, "-out=" + @file_path + ".wc"] @command =[ "texcount", "-nocol", "-inc", @file_path, "-out=" + @file_path + ".wc"]
@CommandRunner.run @CommandRunner.run
.calledWith("#{@project_id}-#{@user_id}", @command, @directory, @image, @timeout, {}) .calledWith("#{@project_id}-#{@user_id}", @command, @directory, @image, @timeout, {})
.should.equal true .should.equal true

View file

@ -59,3 +59,21 @@ describe "LatexRunner", ->
mainFile = command.slice(-1)[0] mainFile = command.slice(-1)[0]
mainFile.should.equal "$COMPILE_DIR/main-file.tex" mainFile.should.equal "$COMPILE_DIR/main-file.tex"
describe "with a flags option", ->
beforeEach ->
@LatexRunner.runLatex @project_id,
directory: @directory
mainFile: @mainFile
compiler: @compiler
image: @image
timeout: @timeout = 42000
flags: ["-file-line-error", "-halt-on-error"]
@callback
it "should include the flags in the command", ->
command = @CommandRunner.run.args[0][1]
flags = command.filter (arg) ->
(arg == "-file-line-error") || (arg == "-halt-on-error")
flags.length.should.equal 2
flags[0].should.equal "-file-line-error"
flags[1].should.equal "-halt-on-error"

View file

@ -1,6 +1,7 @@
SandboxedModule = require('sandboxed-module') SandboxedModule = require('sandboxed-module')
sinon = require('sinon') sinon = require('sinon')
require('chai').should() require('chai').should()
expect = require('chai').expect
modulePath = require('path').join __dirname, '../../../app/js/RequestParser' modulePath = require('path').join __dirname, '../../../app/js/RequestParser'
tk = require("timekeeper") tk = require("timekeeper")
@ -22,7 +23,7 @@ describe "RequestParser", ->
resources: [] resources: []
@RequestParser = SandboxedModule.require modulePath, requires: @RequestParser = SandboxedModule.require modulePath, requires:
"settings-sharelatex": @settings = {} "settings-sharelatex": @settings = {}
afterEach -> afterEach ->
tk.reset() tk.reset()
@ -55,7 +56,7 @@ describe "RequestParser", ->
beforeEach -> beforeEach ->
delete @validRequest.compile.options.compiler delete @validRequest.compile.options.compiler
@RequestParser.parse @validRequest, (error, @data) => @RequestParser.parse @validRequest, (error, @data) =>
it "should set the compiler to pdflatex by default", -> it "should set the compiler to pdflatex by default", ->
@data.compiler.should.equal "pdflatex" @data.compiler.should.equal "pdflatex"
@ -66,6 +67,21 @@ describe "RequestParser", ->
it "should set the imageName", -> it "should set the imageName", ->
@data.imageName.should.equal "basicImageName/here:2017-1" @data.imageName.should.equal "basicImageName/here:2017-1"
describe "with flags set", ->
beforeEach ->
@validRequest.compile.options.flags = ["-file-line-error"]
@RequestParser.parse @validRequest, (error, @data) =>
it "should set the flags attribute", ->
expect(@data.flags).to.deep.equal ["-file-line-error"]
describe "with flags not specified", ->
beforeEach ->
@RequestParser.parse @validRequest, (error, @data) =>
it "it should have an empty flags list", ->
expect(@data.flags).to.deep.equal []
describe "without a timeout specified", -> describe "without a timeout specified", ->
beforeEach -> beforeEach ->
delete @validRequest.compile.options.timeout delete @validRequest.compile.options.timeout
@ -88,7 +104,7 @@ describe "RequestParser", ->
it "should set the timeout (in milliseconds)", -> it "should set the timeout (in milliseconds)", ->
@data.timeout.should.equal @validRequest.compile.options.timeout * 1000 @data.timeout.should.equal @validRequest.compile.options.timeout * 1000
describe "with a resource without a path", -> describe "with a resource without a path", ->
beforeEach -> beforeEach ->
delete @validResource.path delete @validResource.path
@ -175,7 +191,7 @@ describe "RequestParser", ->
it "should return the url in the parsed response", -> it "should return the url in the parsed response", ->
@data.resources[0].url.should.equal @url @data.resources[0].url.should.equal @url
describe "with a resource with a content attribute", -> describe "with a resource with a content attribute", ->
beforeEach -> beforeEach ->
@validResource.content = @content = "Hello world" @validResource.content = @content = "Hello world"
@ -185,7 +201,7 @@ describe "RequestParser", ->
it "should return the content in the parsed response", -> it "should return the content in the parsed response", ->
@data.resources[0].content.should.equal @content @data.resources[0].content.should.equal @content
describe "without a root resource path", -> describe "without a root resource path", ->
beforeEach -> beforeEach ->
delete @validRequest.compile.rootResourcePath delete @validRequest.compile.rootResourcePath
@ -225,13 +241,13 @@ describe "RequestParser", ->
} }
@RequestParser.parse @validRequest, @callback @RequestParser.parse @validRequest, @callback
@data = @callback.args[0][1] @data = @callback.args[0][1]
it "should return the escaped resource", -> it "should return the escaped resource", ->
@data.rootResourcePath.should.equal @goodPath @data.rootResourcePath.should.equal @goodPath
it "should also escape the resource path", -> it "should also escape the resource path", ->
@data.resources[0].path.should.equal @goodPath @data.resources[0].path.should.equal @goodPath
describe "with a root resource path that has a relative path", -> describe "with a root resource path that has a relative path", ->
beforeEach -> beforeEach ->
@validRequest.compile.rootResourcePath = "foo/../../bar.tex" @validRequest.compile.rootResourcePath = "foo/../../bar.tex"