[Tex/LaTex] Tikz externalize not working when jobname is specified for main document

currfiletikz-externaltikz-pgf

I'm not sure if it's a well known fact or not, but using the tikz externalization library does not work very well if a LaTeX document is compiled with a jobname that is different from the filename of that document.

For example, if the following MWE is compiled using pdflatex (note the -jobname output parameter), it won't work unless the file is named output.tex (for convenience, let's say it's called main.tex):

%& -jobname=output
\documentclass{book}

\usepackage{tikz,pgfplots}
\usetikzlibrary{external}
\tikzset{external/only named=true}
\pgfplotsset{compat=newest}

\tikzexternalize

\begin{document}

\begin{figure}[h]
  \tikzsetnextfilename{sample_tikz}
  \begin{tikzpicture}[scale=1.50]
    \begin{axis}\addplot {x^3};\end{axis}
  \end{tikzpicture}
  \caption{Sample tikz picture.}
\end{figure}

\end{document}

If compiled without the -jobname parameter, it works just fine, whereas with the parameter, I get an error that looks something like this:

===== 'mode=convert with system call': Invoking 'pdflatex -shell-escape -halt-o
n-error -interaction=batchmode -jobname "sample_tikz" "\def\tikzexternalrealjob
{output}\input{output}"' ========

! Package tikz Error: Sorry, the system call 'pdflatex -shell-escape -halt-on-e
rror -interaction=batchmode -jobname "sample_tikz" "\def\tikzexternalrealjob{ou
tput}\input{output}"' did NOT result in a usable output file 'sample_tikz' (exp
ected one of .pdf:.jpg:.jpeg:.png:).

From the error message, it is also quite obvious what goes wrong. The default externalization system call looks something like this:

pdflatex \tikzexternalcheckshellescape -halt-on-error -interaction=batchmode -jobname "\image" "\texsource"

where the macro \texsource is \edef'd as

\string\def\string\tikzexternalrealjob{\tikzexternal@realjob}\string\input{\tikzexternal@realjob}

and \tikzexternal@realjob is set based on the value of \jobname used when compiling main.tex. When main.tex is compiled with -jobname output, \texsource expands into

\def\tikzexternalrealjob{output}\input{output}

which is problematic, because main.tex isn't called output.tex.

A possible solution

I've tried to look at the tikz external source code, and experimented with how to approach this problem.
My reasoning has been that I want the \texsource somehow to expand to

\def\tikzexternalrealjob{output}\input{main}

instead (note that \tikzexternalrealjob must still be \def'd to the actual \jobname, because it's used in the tikz externalization code to decide whether the current job is for the main document or for the externalization of a tikz image).

I was inspired by this post to use the currfile package (thanks @martin-scharrer) to retrieve the name main automatically.

What I've come up with so far (which seems to work) is the following:

Change the MWE to:

%& -jobname=output -recorder
\documentclass{book}

\usepackage{tikz,pgfplots}
\usetikzlibrary{external}
\tikzset{external/only named=true}
\pgfplotsset{compat=newest}

\input{externalize-hack.tex} % <---- THIS WAS ADDED

\tikzexternalize

\begin{document}

\begin{figure}[h]
  \tikzsetnextfilename{sample_tikz}
  \begin{tikzpicture}[scale=1.50]
    \begin{axis}\addplot {x^3};\end{axis}
  \end{tikzpicture}
  \caption{Sample tikz picture.}
\end{figure}

\end{document}

and let the file externalize-hack.tex be defined as the following:

\usepackage{currfile-abspath}
\getmainfile

\usepackage{xpatch}

\makeatletter

\AtEndDocument{%
  \newwrite\maindeffile%
  \immediate\openout\maindeffile=\jobname.maindef%
  \immediate\write\maindeffile{%
    \noexpand\ifcsname mymainfile\noexpand\endcsname\noexpand\else%
      \noexpand\gdef\noexpand\mymainfile{\mymainfile}%
    \noexpand\fi%
  }%
  \immediate\closeout\maindeffile%
}

\@input{\jobname.maindef}

\ifcsname mymainfile\endcsname%
  \ifx\mymainfile\@empty%
    % Make a second attempt at extracting the real file name
    % (necessary because currfile needs two runs on MiKTeX..)
    \filename@parse{\themainfile}%
    \let\mymainfile\filename@base%
    \ifx\mymainfile\@empty%
      % If still empty, fallback to default behavior
      \def\mymainfile{\tikzexternal@realjob}%
    \fi%
  \fi%
\else%
  % First attempt at extracting real file name
  \filename@parse{\themainfile}%
  \let\mymainfile\filename@base%
\fi%

\xpatchcmd{\tikzexternal@assemble@systemcall}%
  {%
    \edef\texsource{%
      \string\def\string\tikzexternalrealjob{\tikzexternal@realjob}%
      \string\input{\tikzexternal@realjob}%
    }%
  }%
  {%
    \edef\texsource{%
      \string\def\string\tikzexternalrealjob{\tikzexternal@realjob}%
      \string\def\string\mymainfile{\mymainfile}%
      \string\input{\mymainfile}%
    }%
  }%
  {}
  {}

\let\old@tikzexternalize@opt@withname\tikzexternalize@opt@withname%
\def\tikzexternalize@opt@withname[#1]#2{%
  % Suppress externalization as long as \mymainfile is empty
  % (This will only be the case the one or two first compilation runs!)
  \ifx\mymainfile\@empty\else%
    \old@tikzexternalize@opt@withname[#1]{#2}%
  \fi%
}

\makeatother

Also note the -recorder option that was added, as required by the currfile package.

However, to me this hack feels somewhat clumsy. Hence, I would greatly appreciate any input from you guys!

Best Answer

You got that tikz uses \jobname to compose the command line that generates external pics. So redefining \jobname and at the same time using tikz externalization is too much touble.

A possible work-around:

  • use \tikzsetfigurename{something} to set basename for external tikz pictures;
  • call pdflatex without -jobname output everytime you change a tikzpicture, so it correctly generates figures;
  • with external figures gerenated, you can change everything in your text at will and call pdflatex -jobname output because only checksums are computed without need of real jobname.

It worked with my MWE:

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{external}
\tikzexternalize
\tikzsetfigurename{something}

\begin{document}

\tikz \draw (0,0) -- (1,0.5);

\tikz \draw[red] (0,0) -- (1,0.5);

\end{document}

with call sequence:

  • pdflatex -shell-escape main (here figures somethingN are generated)
  • pdflatex -shell-escape -jobname output main
Related Question