LaTeX Makefile for Unix



There is a variaty of solutions out there to compile your LaTeX files. Among the more popular are IDE's such as TeXMaker which allow for comfortable editing and viewing of your documents, als well as suites like VIm-LaTeX, which I prefer.

While the IDE's give you a comfortable way to call pdflatex, you usually don't have much influence on how the toolchain is invoked, while the VIm solution makes you call all the tools yourself.

A good alternative to this, is the use of a MAKE tool. With the right Makefile you only need to invoke Make once, and can set all sorts of different results. There are some very good makefiles out there, up to now, I used the solution by Chris Monson.
Monson's Makefile allows for serveral documents, has a very pretty output and allows to select between dvi and pdf output with a single command-line switch.

Since I publish texts on pages such as I need to save my texts as LibreOffice and EPub files, as well as DVI and PDF. Since Chris' Makefile doesn't do this by default, I made my own script.

Since I loathe writing GNU-Makefiles, I started working with Waf. The good thing about waf, besides the fact that it uses simple easy-to-read Python-code, is that it has generic support for TeX and LaTeX.
The script also supports the use of htlatex (mk4ht), to produce html, odt and even ePub-files.


To use the script you need to install the waf-build system. Waf is basically a python library combined with a script. On most Linux/Unix platforms Python is already installed.

To use waf for TeX/LaTeX, you need the following packages to be installed:
On my Arch-Linux system, installation is done with a few keystrokes, because waf is in AUR. I recommend using yaourt as a frontend for AUR, since it limits the installation procedure to a simple:

yaourt -S waf

To install on other systems, such as Ubuntu, a little more command-line magic is required. First download the package from the waf-homepage.
Extract it with

tar -jxvf waf-1.7.2.tar.bz2

into a directory of your choice. And cd into it:

cd waf-1.7.2

Now you can create the script by calling

python waf-lite
chmod 755 waf

You can use the waf-script immediately, but I recommend to copy it somewhere, where you can access it from everywhere. I chose to copy it to the /opt directory.

sudo mkdir /opt/waf
sudo chmod w+a /opt/waf
cp waf /opt/waf
alias waf /opt/waf/waf

Copy the alias command into your .bashrc, to make it permanent. Making the /opt/waf directory world writeable is mandatory, to allow the script to unpack the library.

As a last step copy the wscript file into your LaTeX source directory.



Invoking the waf script is easy:

waf configure build

This call will simply walk through all .tex files in your directory, and produce a pdf file with pdflatex in the subdirectory ./build for each file found.
Actually, this command is performing two steps, you could also do seperatly. First it runs

waf configure

This will seek out a location of all the required programs (such as your TeX/LaTeX installation) and create a list of files to be processed.
As long as you don't add new main-files, you don't need to do this again.

waf build

This does the actual processing and produces the output.

The above method only works however, when you organize your tex files in such a way, that you put all input files in a separate folder, and only keep your main files in the top directory. I recommend that you do that anyway, but if you didn't, you need to tell waf which file is your main LaTeX file during configure.

waf configure --infile=MyMainFile.tex
waf build

Of course you can combine the commands as before:

waf configure build --infile=MyMainFile.tex

Waf keeps track of all the files it has created, so to clean up the ./build directory, you can do

waf clean

If you want to clean up everything, including the configuration, you can do:
waf distclean

This will leave the directory in the state it was, before you called waf for the first time. To get a clean slate, and recreate your document with all support files, you can call the trident:
waf distclean configure build

Advanced uses

If you don't want the script to create a pdf file, you can use the --form switch to change the output format. Valid values for this are:
If you want to create a DVI file, just pass the --form parameter to the configure stage:

waf configure --form=latex

The epub Option has some extra features, that can be useful to create an ebook. First of all, it searches your main-file for a comment line looking like this:

% Title: My super cool document
% Author: John Doe

If you have included lines in that form somewhere in your main document, the title will be used as the book-title, and the author as the book-author.

Additionally, you can give an extra parameter --cover that will add a given image as the books cover image. This parameter currently only works with the epub option.

waf configure --form=epub --cover=CoverImage.png

Note: To use this feature, you need to have Calibre installed

To put your newly created files in a useful place (not the build-directory) you can use the install command.

waf install --prefix=/path/to/ebook-reader

Note: Make sure you always have the --prefix options set. The default prefix for waf is /usr/local, which is likely to be not writable!

Spell Checking

If you are writing longer texts, you can call

waf configure spellck

to initiate a spellcheck of all of your files, except the main file. Right now the script assumes you are keeping your files in the chapters directory in your project directory. You can change that default by setting the chapterpath variable at the beginning of your wscript. This function assumes that you are using a main project file in the root-folder, and keep all the text files in your chapters folder, which you import into the main file by using the
\import{} statement in LaTeX.

If you want to spellcheck a single file, you can use the --infile parameter:

waf configure spellck --infile=MyFile.tex

The script will then use only this one file for the spellcheck.


You can use this script for documents that use the SetupHandwrittenFonts handlatex package.
Simply use the --form=handlatex option. This will create a pdf document, that looks like it is handwritten.
You need to use the 'hand' package in your document and have the handlatex package install, or else the build will fail!


The whole script can be downloaded here. It is licensed under the GNU General Public License 3.0 so feel free to use, copy and modify it.

To download the script, click the Grab button below the code.

wscript (line 1)
  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. # Created On: Tue 28 Aug 2012 11:44:05  CEST
  4. # Last Modified: Sun 12 Apr 2015 01:15:41 pm CEST
  5. # MAKEFILE for compiling LaTeX Files
  7. VERSION='1.0.0'
  10. top='.'
  11. out='build'
  12. chapterpath='./chapters'
  14. # query the options that are possible outputs
  15. def options(ctx):
  16.     ctx.add_option('--form',action='store',default='pdflatex',help='Specify the output format. Allowed values are: pdflatex, latex, htlatex, oolatex, epub, handlatex')
  17.     ctx.add_option('--cover',action='store',help='Specify an image as cover for ePubs.\nOnly works with --form=epub')
  18.     ctx.add_option('--infile',action='store',help='Specify the tex file to use as input file.\nIf not given, all files in the top level directory will be compiled.')
  19.     ctx.add_option('--spellckpath',action='store',help='Specify the path in which to look for the content files for spellcheck.\nDefault is ./chapters',default=chapterpath);
  21. # configure script
  22. def configure(ctx):
  23.     # local functions to configure the selected element
  24.     def find_tex_files(path):
  25.         import os
  26.         ret = []
  27.         for name in os.listdir(path):
  28.             if os.path.isfile(os.path.join(path,name)):
  29.                 if (name.endswith('.tex')) or (name.endswith('.ltx')):
  30.                     ret.append(name)
  31.         return ret
  33.     def conf_tex(ctx):
  34.         ctx.load('tex')
  36.     def conf_htlatex(ctx):
  37.         ctx.find_program('mk4ht',var='TEX')
  38.         ctx.env.TEXINPUT = ctx.path.abspath() + '//'
  40.     def conf_epub(ctx):
  41.         ctx.find_program('mk4ht',var='TEX')
  42.         ctx.find_program('ebook-convert',var='EPUB')
  43.         ctx.env.TEXINPUT = ctx.path.abspath() + '//'
  45.     def conf_handlatex(ctx):
  46.         ctx.find_program('handlatex',var='TEX')
  47.         ctx.env.TEXINPUT = ctx.path.abspath() + '//'
  49.     # define a structure to assign the functions to the parameters
  50.     conf_output = {'pdflatex' : conf_tex,
  51.               'latex' : conf_tex,
  52.               'htlatex'  : conf_htlatex,
  53.               'oolatex'   : conf_htlatex,
  54.               'epub'  : conf_epub,
  55.               'handlatex' : conf_handlatex
  57.     }
  59.     # set the selected mode
  60.     if ctx.options.form:
  61.         ctx.env.FORM = ctx.options.form
  62.         conf_output[ctx.env.FORM](ctx)
  63.     else:
  64.         ctx.fatal('Output format is not set!')
  66.     # set a cover image
  67.     if ctx.options.cover:
  68.         ctx.env.COVER = ctx.options.cover
  69.     else:
  70.         ctx.env.COVER = ''
  72.     # find the aspell program
  73.     ctx.find_program('aspell',var='ASPELL')
  75.     # set a default path to look for spellck sources
  76.     if ctx.options.spellckpath:
  77.         ctx.env.SPELLCK = ctx.options.spellckpath
  79.     # Set a list of all files to use
  80.     if ctx.options.infile:
  81.         sf = []
  82.         sf.append(ctx.options.infile)
  83.         ctx.env.SOURCEFILES = sf
  84.     else:
  85.         ctx.env.SOURCEFILES = find_tex_files(top)
  88. def build(ctx):
  89.     # metadata parameters
  90.     def build_metadata(fileName,cover):
  91.         ret = '--level1-toc \'//*[@class="title"]\' '
  92.         with open(fileName,'r') as in_file:
  93.             count = 0
  94.             for line in in_file:
  95.                 if '% Author' in line:
  96.                     ret += ' --authors "' + line.split(':')[1].rstrip().lstrip() +'"'
  97.                     count += 1
  98.                 elif '% Title' in line:
  99.                     ret += ' --title "' + line.split(':')[1].rstrip().lstrip() + '"'
  100.                     count += 1
  101.                 elif count > 1:
  102.                     break
  103.             in_file.close()
  105.             if cover:
  106.                 ret += ' --cover "' + cover + '"'
  108.             return ret
  110.     # local functions to control the build process
  111.     def build_tex(ctx):
  112.         import os
  113.         for f in ctx.env.SOURCEFILES:
  114.             ctx(features = 'tex',
  115.                 type     = ctx.env.FORM,
  116.                 source   = f,
  117.                 prompt   = 0
  118.                 )
  119.             if ctx.cmd == 'install':
  120.                 outExt = '.dvi'
  121.                 if ctx.env.FORM == 'pdflatex':
  122.                     outExt = '.pdf'
  123.                 fileName, fileExt = os.path.splitext(f)
  124.                 ctx.install_files('${PREFIX}',fileName + outExt)
  126.     def build_htlatex(ctx):
  127.         for f in ctx.env.SOURCEFILES:
  128.             ctx(rule='TEXINPUTS=${TEXINPUT}: ${TEX} ${FORM} ${SRC} >/dev/null',source=f)
  129.             if ctx.cmd == 'install':
  130.                 fileName, fileExt = os.path.splitext(f)
  131.                 ctx.install_files('${PREFIX}',fileName + '.html')
  133.     def build_epub(ctx):
  134.         import os
  135.         for f in ctx.env.SOURCEFILES:
  136.             print(ctx.path.find_resource(ctx.env.COVER))
  137.             if ctx.env.COVER :
  138.                 ctx.env.META = build_metadata(f,ctx.path.find_resource(ctx.env.COVER).abspath())
  139.             else:
  140.                 ctx.env.META = build_metadata(f,'')
  141.             fileName, fileExt = os.path.splitext(f)
  142.             ctx(rule='TEXINPUTS=${TEXINPUT}: ${TEX} htlatex ${SRC} >/dev/null',source=f, target=fileName + '.html')
  143.             ctx(rule='${EPUB} ${SRC} ${TGT} ${META}',source=fileName + '.html', target=fileName + '.epub')
  144.             if ctx.cmd == 'install':
  145.                 ctx.install_files('${PREFIX}',fileName + '.epub')
  147.     def build_handlatex(ctx):
  148.         import os
  149.         for f in ctx.env.SOURCEFILES:
  150.             ctx(rule='TEXINPUTS=${TEXINPUT}: ${TEX} ${SRC} >/dev/null && rm -f ../*.h*',source=f)
  151.             if ctx.cmd == 'install':
  152.                 fileName, fileExt = os.path.splitext(f)
  153.                 ctx.install_files('${PREFIX}',fileName + outExt)
  156.     # define a structure to assign the functions to the parameters
  157.     build_output = {'pdflatex'   : build_tex,
  158.             'latex' : build_tex,
  159.             'htlatex'  : build_htlatex,
  160.             'oolatex'   : build_htlatex,
  161.             'epub'  : build_epub,
  162.             'handlatex' : build_handlatex
  163.     }
  165.     # call the configured build method
  166.     build_output[ctx.env.FORM](ctx)
  169. # Custom command to spellcheck all
  170. def spellck(ctx):
  172.     # Search for all possible TEX files
  173.     def find_tex_files(path):
  174.         import os
  175.         ret = []
  176.         for root, dirs, files in os.walk(path):
  177.             for name in files:
  178.                 if (name.endswith('.tex')) or (name.endswith('.ltx')):
  179.                     ret.append(os.path.join(root,name))
  180.         return ret
  182.     # Set a list of input files to use
  183.     if ctx.options.infile:
  184.         sf = []
  185.         sf.append(ctx.options.infile)
  186.         ctx.env.SOURCEFILES = sf
  187.     else :
  188.        ctx.env.SOURCEFILES = find_tex_files(ctx.env.SPELLCK)
  190.     # run aspell
  191.     from os import system
  192.     for f in ctx.env.SOURCEFILES :
  193.         system(ctx.env.ASPELL + ' -c ' + f)
  194.         system('rm ' + f + '.' +'bak')
  196. # Class declarartion to bind the build context to the spellck command
  197. from waflib.Build import BuildContext
  198. class spck(BuildContext):
  199.     cmd='spellck'
  200.     fun='spellck'

There are no comments on this page.
Valid XHTML :: Valid CSS: :: Powered by WikkaWiki