2021/04/03: Bazel Latex Rules

Recently, I found that there was some interest in latex rules for bazel. Moreover, I found increased hits on my old blog entry on timestamps with pdflatex, possibly because it is referenced from Ed's latex rules. So I decided, that there should be a more accessible place for my latex rules that an old mail thread.

So, here's an archive of (only) the relevant parts of my personal bazel repository containing my bazel latex rules. There are a few things to keep in mind in comparison to Ed's rules.

As thinking about latex rules for bazel step by step leads through all features of bazel's rule system, I used them as an example on how to extend bazel in my FrOSCon 2017 talk. The video recording is still online, and the relevant part starts at 0:16:45. The slides are also available online.

Let's have a look on how to use these rules.

Simple use via filegroup

The most simple, but still most typical, use case is to define a filegroup for each reusable part and combine them to a larger document by including the entry points and declaring the file groups as sources (for a latex document) or dependencies (for another filegroup forming a larger reusable part).

So, assume you have the following main.tex.


\documentclass{beamer}
\begin{document}
\input{example/slides_triangle}
\input{example/slides_square}
\end{document}
download

Then, your BUILD files could look as follows.
load("//aehlig_rules/latex:latex.bzl", "latex")

filegroup(
    name = "slides_triangle",
    srcs = [
        "slides_triangle.tex",
        "triangle.eps",
    ],
)

filegroup(
    name = "slides_square",
    srcs = [
        "slides_square.tex",
        "square.eps",
    ],
)

latex(
    name = "example",
    main = "main.tex",
    srcs = [
        ":slides_triangle",
        ":slides_square",
    ],
)
download

You can also download the full example. This BUILD file defines a target :example to build the tex document. For convenience, the macro latex also defines another target :xpdf_example, so that bazel run :xpdf_example can be used to view the resulting pdf file.

Variants of the same document

I often need several variants of the same latex document, e.g., a full mathematical script, as well as an outline (say, without proofs). To avoid duplication, those should come from the same sources; the only thing I need is to include a single short file to switch between the variants. So there is a single exception to the rule that each file is included by its canonical path: the file specified by the optional flavor argument will always be visible at the location flavor.tex in the working directory of the latex invocation (i.e., no module path needed). In this way, I can conveniently define my variants by a BUILD file like the following.


load("//aehlig_rules/latex:latex.bzl", "latex")

[
latex(
    name="main_%s" % (flavor,),
    main="main.tex",
    flavor = "%s.tex" % (flavor,)
) for flavor in ["full", "outline"]]
download

The main.tex simply includes flavor.tex.
\documentclass{article}
\usepackage{amsthm}
\input{flavor}

\newtheorem{theorem}{Theorem}

\begin{document}

\begin{theorem}
  $(a+b)^2=a^2 + 2ab + b^2$
\end{theorem}
\begin{proof}
  $(a+b)^2=(a+b)(a+b)=a(a+b)+b(a+b)= a^2+ab+ba+b^2=a^2+ab+ab+b^2=a^2+2ab+b^2$
\end{proof}

\end{document}
download

The file full.tex is simply an empty file, wheras outline.tex redefines the proof environment.
\renewenvironment{proof}%
  {\bgroup\setbox0=\vbox\bgroup}%
  {\egroup\egroup}
download

Including a file group

Another typical siutation when writing larger latex documents is that each part brings in its own contribution to the part of the document before the \begin{document}. Packages have to be included, macros for all the notations used have to be defined (so that I have a single place to change my notation, and in this way don't get inconsistencies in my document, if I chose to change my notation), etc.

Collecting all those with nested file groups is a great way to get the right collection of files to be included in preamble—due to the set-like semantics already in deduplicated form. To include all the files of that file group, I use the latex_include rule. It generates a .tex file with one \input for each member of the file group (in iteration order). Additionally, it passes via the appropriate provider, that every latex target using this file also, implicitly, has the whole file group as input.

So, in my main.tex I simply have one \input{path/to/module/preamble} just after the \begin{document} and use a BUILD file like the following.


load("//aehlig_rules/latex:latex.bzl", "latex", "latex_include")

filegroup(
    name = "local_preamble",
    srcs = [
        "//latex/shared:use_amssymb.tex",
        "notation.tex",
        # ...
    ],
)

latex_include(
    name = "preamble",
    srcs = [
        ":local_preamble",
        # likely also file groups from dependencies
    ],
)

latex(
    name = "main",
    main = "main.tex",
    srcs = [
        ":preamble",
        # other dependencies
    ],
)
download

Verbatim files

It is often useful to include files verbatim in a document, e.g., console "screen shots" generated via script(1). If that interaction is sufficiently reproducible, that often is itself a generated artifact. To conveniently include those files, the rules come with a macro latex_verbatim that generates a new .tex file from the source file by prepending it with \begin{verbatim} and appending \end{verbatim} at the end. Yes, this is technically unsound as it assumes that the string \end{verbatim} does not occur in the file to be quoted. But in practce, that simple macro just works too well.

Related: staged postscript files

While technically not a latex problem, something I frequently have to do when generating slides with latex is to have diagrams developped step by step. Of course, I want to maintain the whole diagram with all its stages in a single file, in my case a postscript file. Also, while the diagram can be reused, the stages of the development I want to include in a presentation are differnt at different occasions.

So, my approach is to have a file, that is parametric in the variable stage defined at the beginning of the document; for debugging positioning problems it is also useful to optionally draw the bounding box.


%!
%%BoundingBox: -1 -1 101 151

gsave

/stage { 9 } bind def
/drawbb { true } bind def

drawbb {
    gsave
    5 setlinewidth
    newpath
    0 0 moveto
    100 0 rlineto
    0 150 rlineto
    -100 0 rlineto
    closepath
    stroke
    grestore
} if

newpath
0 0 moveto
stage 1 ge { 0 100 rlineto } if
stage 2 ge { 50 50 rlineto } if
stage 3 ge { 50 -50 rlineto } if
stage 4 ge { -100 0 rlineto } if
stage 5 ge { 100 -100 rlineto } if
stage 6 ge { 0 100 rlineto } if
stage 7 ge { -100 -100 rlineto } if
stage 8 ge { 100 0 rlineto } if
stroke

grestore
download

Now, ps_family creates from that input one target for every stage, as well as a file group consisting of all those targets. Each of those targets generates a new eps file from the original one by replacing the definitions of stage and drawbb (the latter by false, unless I specify drawbb = "true" in the BUILD file).
load("//aehlig_rules/latex:latex.bzl", "latex")
load("//aehlig_rules/ps:psfamily.bzl", "ps_family")

ps_family(
    name = "drawing",
    src = "drawing.eps",
    stages = [0, 1, 2, 3, 4, 5, 6, 7, 8],
)

latex(
    name = "main",
    main = "main.tex",
    srcs = [ ":drawing" ],
)
download

The generated files can be included as usual (here, I assume that the module is ps_example.
\documentclass{beamer}
\begin{document}

\begin{frame}{A drawing}
  This is how you draw that image as one line.

  \only<1>{\includegraphics[height=0.7\textheight]{ps_example/drawing_0}}%
  \only<2>{\includegraphics[height=0.7\textheight]{ps_example/drawing_1}}%
  \only<3>{\includegraphics[height=0.7\textheight]{ps_example/drawing_2}}%
  \only<4>{\includegraphics[height=0.7\textheight]{ps_example/drawing_3}}%
  \only<5>{\includegraphics[height=0.7\textheight]{ps_example/drawing_4}}%
  \only<6>{\includegraphics[height=0.7\textheight]{ps_example/drawing_5}}%
  \only<7>{\includegraphics[height=0.7\textheight]{ps_example/drawing_6}}%
  \only<8>{\includegraphics[height=0.7\textheight]{ps_example/drawing_7}}%
  \only<9>{\includegraphics[height=0.7\textheight]{ps_example/drawing_8}}%

\end{frame}

\end{document}
download