LatexMk relative directory compilation hook

A method for finding project root in emacs

configuration

emacs

2023-09-17


During my quest to draw battle diagrams for a certain novel, I stumbled across this LaTeX problem. Suppose we have a file directory in this form:

$ROOT
├ .latexmkrc
├ cv.tex
├ application.cls
├ applications
│ ├ grant-application.tex
│ └ fellowship-application.tex
└ build

and we want all the PDF output files to be in the build/ directory. If we want to compile grant-application.tex, we need the command

$ latexmk applications/grant-application.tex

However, the default behaviour in tex.el is that it executes latexmk in the directory of the “master file”, i.e. applications/. This is a problem since this file does not contain application.cls which we want to reference. Moreover latexmk sometimes cannot find .latexmkrc when executed in applications/.

In order to solve this issue, we modify the 'TeX-master-file and 'TeX-master-directory functions before entering the 'TeX-command-run-all function. This makes use of function advising.

(defun custom/TeX-compile-in-root ()
		"Execute ~TeX-command-run-all~ in the nearest ~.latexmkrc~ directory or
		projectile project root."
		(interactive)
		(let*
			((dir-latexmkrc
				(projectile-locate-dominating-file default-directory ".latexmkrc"))
			(dir-projectile (projectile-project-root))
			(is-latexmk-child-of-projectile (string-match-p (regexp-quote dir-projectile) dir-latexmkrc))
			(dir-work (cond
				((and dir-latexmkrc is-latexmk-child-of-projectile) dir-latexmkrc)
				(dir-projectile dir-projectile)
				(t default-directory)))
			(dir-current-rel (string-remove-prefix dir-work default-directory))
			(file-current-rel (concat dir-current-rel (TeX-master-file))))
			; Execute in shadowed ~default-directory~
			(cl-labels ((replace-pwd () dir-work) (replace-file (&optional extension nondirectory ask) file-current-rel))
				(advice-add 'TeX-master-directory :override #'replace-pwd)
				(advice-add 'TeX-master-file :override #'replace-file)
				(TeX-command-run-all nil)
				(advice-remove 'TeX-master-file #'replace-file)
				(advice-remove 'TeX-master-directory #'replace-pwd)
		)))

When executed on grant-application.tex, this replaces

TeX-master-file: grant-application
TeX-master-directory: $ROOT/applications

with

TeX-master-file: applications/grant-application
TeX-master-directory: $ROOT

and the problem is solved.

A yet more elegant way is to globally advice 'TeX-master-file and 'TeX-master-directory'. We make use of the Advice Combinators:

(defun custom/TeX-root-directory (start)
	(let* (
		(dir-latexmkrc (projectile-locate-dominating-file start ".latexmkrc"))
		(dir-projectile (projectile-project-root))
		(is-latexmk-child-of-projectile (and dir-projectile dir-latexmkrc
			(string-match-p (regexp-quote dir-projectile) dir-latexmkrc))))
		(cond
			((and dir-latexmkrc is-latexmk-child-of-projectile) dir-latexmkrc)
			(dir-projectile dir-projectile)
			(t start))))
(defun custom/TeX-master-directory-filter (dir-master)
	(custom/TeX-root-directory dir-master))
(defun custom/TeX-master-file-filter (file-master)
	(concat
		(string-remove-prefix (custom/TeX-root-directory default-directory) default-directory)
		file-master
	))

(advice-add 'TeX-master-directory :filter-return #'custom/TeX-master-directory-filter)
(advice-add 'TeX-master-file      :filter-return #'custom/TeX-master-file-filter)

One caveat which could happen is if the subdirectory contains a .tex file with the same name as the parent directory:

$ROOT
├ ...
├ cv.tex
├ applications
│ ├ cv.tex
│ └ ...
└ build

which would result in clashing PDF names in build/.


Created and Designed by Leni Aniva based on Tokiwa