From 422ac30e6c9e934382c29aba504e049671e6f906 Mon Sep 17 00:00:00 2001 From: claude Date: Sun, 31 May 2026 19:27:39 +0000 Subject: [PATCH] Support LaTeX and Quarto compilation in parallel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Verso now compiles both .tex (latexmk) and .qmd (Quarto) projects, dispatching by the root file's extension rather than replacing one with the other. LaTeX and Quarto projects can coexist on the same server. CompileManager: re-import LatexRunner and add a _getRunner() dispatcher that returns a uniform {run, isRunning, kill} interface. .qmd/.md/.Rmd → QuartoRunner; everything else (.tex/.ltx/.Rtex/.Rnw) → LatexRunner. stopCompile now checks/kills both runners since it has no root path. compiler-setting.tsx: restore the LaTeX engine choices (pdfLaTeX, LaTeX, XeLaTeX, LuaLaTeX) alongside Quarto. The dropdown still controls which TeX engine latexmk uses; actual engine dispatch is by file extension. Dockerfile-base: reinstall TeX Live alongside Quarto (texlive-full minus -doc/-lang- packages, plus xetex/luatex/biber/latexmk/texcount/chktex/ synctex). Restore TEXMFVAR for a writable LuaTeX cache. This brings back a large image, which is the accepted cost of full LaTeX+Quarto support. Co-Authored-By: Claude Sonnet 4.6 --- server-ce/Dockerfile-base | 24 +++++++++++++ services/clsi/app/js/CompileManager.js | 36 +++++++++++++++++-- .../compiler-settings/compiler-setting.tsx | 11 +++++- 3 files changed, 68 insertions(+), 3 deletions(-) diff --git a/server-ce/Dockerfile-base b/server-ce/Dockerfile-base index f63cc9a8c8..8fb816d278 100644 --- a/server-ce/Dockerfile-base +++ b/server-ce/Dockerfile-base @@ -4,6 +4,10 @@ FROM phusion/baseimage:noble-1.0.3 +# Makes sure LuaTeX cache is writable +# ----------------------------------- +ENV TEXMFVAR=/var/lib/overleaf/tmp/texmf-var + # Update to ensure dependencies are updated # ------------------------------------------ ENV REBUILT_AFTER="2026-05-21" @@ -65,6 +69,26 @@ RUN mkdir -p /opt/quarto-extensions \ \ && chown -R www-data:www-data /opt/quarto-extensions +# Install TeX Live (for compiling .tex projects with latexmk) +# ----------------------------------------------------------------------- +# Verso compiles .qmd with Quarto and .tex with latexmk; both engines live +# side by side. We install (almost) all of texlive-full, excluding the -doc +# and -lang- packages to keep the download from being needlessly huge while +# still providing a complete LaTeX toolchain (latexmk, xetex, lualatex, +# biber, texcount, chktex, synctex, etc.). +# ----------------------------------------------------------------------- +RUN apt-get update \ +&& apt-cache depends texlive-full \ + | grep "Depends: " \ + | grep -v -- "-doc" \ + | grep -v -- "-lang-" \ + | sed 's/Depends: //' \ + | xargs apt-get install -y --no-install-recommends \ +&& apt-get install -y --no-install-recommends \ + texlive-xetex texlive-luatex texlive-bibtex-extra biber \ + latexmk texcount chktex fontconfig inkscape python3-pygments \ +&& rm -rf /var/lib/apt/lists/* + # Set up overleaf user and home directory # ----------------------------------------- diff --git a/services/clsi/app/js/CompileManager.js b/services/clsi/app/js/CompileManager.js index d406bf95ad..4c8799fb1d 100644 --- a/services/clsi/app/js/CompileManager.js +++ b/services/clsi/app/js/CompileManager.js @@ -6,6 +6,7 @@ import logger from '@overleaf/logger' import OError from '@overleaf/o-error' import ResourceWriter from './ResourceWriter.js' import QuartoRunner from './QuartoRunner.js' +import LatexRunner from './LatexRunner.js' import OutputFileFinder from './OutputFileFinder.js' import OutputCacheManager from './OutputCacheManager.js' import ClsiMetrics from './Metrics.js' @@ -39,6 +40,30 @@ const KNOWN_LATEXMK_RULES = new Set([ const LATEX_PASSES_RULES = new Set(['latex', 'lualatex', 'xelatex', 'pdflatex']) +// Quarto handles .qmd/.md/.Rmd sources; everything else (.tex, .ltx, .Rtex, +// .Rnw) is compiled with latexmk via LatexRunner. Dispatch is by the root +// file's extension, so LaTeX and Quarto projects can coexist on one server. +function _isQuartoFile(rootResourcePath) { + return /\.(qmd|md|rmd)$/i.test(rootResourcePath || '') +} + +// Return a runner with a uniform { run, isRunning, kill } interface so the +// rest of CompileManager doesn't need to know which engine is in use. +function _getRunner(rootResourcePath) { + if (_isQuartoFile(rootResourcePath)) { + return { + run: (name, opts) => QuartoRunner.promises.runQuarto(name, opts), + isRunning: name => QuartoRunner.isRunning(name), + kill: name => QuartoRunner.promises.killQuarto(name), + } + } + return { + run: (name, opts) => LatexRunner.promises.runLatex(name, opts), + isRunning: name => LatexRunner.isRunning(name), + kill: name => LatexRunner.promises.killLatex(name), + } +} + function getCompileName(projectId, userId) { if (userId != null) { return `${projectId}-${userId}` @@ -195,8 +220,10 @@ async function doCompile(request, stats, timings) { const compileName = getCompileName(request.project_id, request.user_id) + const runner = _getRunner(request.rootResourcePath) + try { - await QuartoRunner.promises.runQuarto(compileName, { + await runner.run(compileName, { directory: compileDir, mainFile: request.rootResourcePath, compiler: request.compiler, @@ -321,16 +348,21 @@ async function _saveOutputFiles({ async function stopCompile(projectId, userId) { const compileName = getCompileName(projectId, userId) + // stopCompile has no root path, so check both runners — only one can be + // active for a given compileName at a time. + const isRunning = + QuartoRunner.isRunning(compileName) || LatexRunner.isRunning(compileName) const lock = LockManager.getExistingLock(getCompileDir(projectId, userId)) let lockReleased if (lock) { lockReleased = lock.waitForRelease() } else { - if (!QuartoRunner.isRunning(compileName)) return + if (!isRunning) return logger.warn({ projectId, userId }, 'found running compile without lock') lockReleased = Promise.resolve() } await QuartoRunner.promises.killQuarto(compileName) + await LatexRunner.promises.killLatex(compileName) await lockReleased } diff --git a/services/web/frontend/js/features/settings/components/compiler-settings/compiler-setting.tsx b/services/web/frontend/js/features/settings/components/compiler-settings/compiler-setting.tsx index f0e7a38e0b..e84e1cc799 100644 --- a/services/web/frontend/js/features/settings/components/compiler-settings/compiler-setting.tsx +++ b/services/web/frontend/js/features/settings/components/compiler-settings/compiler-setting.tsx @@ -7,7 +7,16 @@ import { usePermissionsContext } from '@/features/ide-react/context/permissions- import { ProjectCompiler } from '@ol-types/project-settings' import { useSetCompilationSettingWithEvent } from '@/features/editor-left-menu/hooks/use-set-compilation-setting' function getCompilerOptions(): Option[] { - return [{ value: 'quarto', label: 'Quarto' }] + // The actual compiler engine is chosen in CLSI by the root file's + // extension (.qmd → Quarto, .tex → latexmk). This dropdown still lets + // LaTeX users pick which TeX engine latexmk should use. + return [ + { value: 'quarto', label: 'Quarto' }, + { value: 'pdflatex', label: 'pdfLaTeX' }, + { value: 'latex', label: 'LaTeX' }, + { value: 'xelatex', label: 'XeLaTeX' }, + { value: 'lualatex', label: 'LuaLaTeX' }, + ] } export default function CompilerSetting() {