Reproducible résumés
Recently, ‘reproducible’ papers have appeared in Academia. The idea is to make all of a paper’s data and code openly available so that readers can reproduce its findings. For software-heavy papers, it makes perfect sense to include code in papers because the code is often as important as the paper’s text.
Outside of Academia, there’s not as much interest in this idea of reproducibility. That’s unfortunate because it’s a really neat idea.
To make this idea more accessible for those outside Academia, I’m going to introduce a new kind of reproducibility. Everyone in the working world needs to apply for a job at some point. So, let’s figure out how to make a résumé, reproducible. This document will be all that is needed to generate my résumé. With some minor adjustments you can use it to generate your own.
At the end of this guide, you should have all of the information needed produce a PDF version of your résume.
1 The Résumé
LaTeX makes it very easy to generate Résumés. It provides extremely powerful typesetting features and can output PDF. Some of the syntax is scary to the unexperienced but I’ve tried to keep it as simple as possible.
LaTeX syntax can be a little confuging at first but basically everything is
made up of commands. Each command starts with a \
followed by a command
name. Each command can take a variable number of arguments and they are
specified with {
and }
. LaTeX is made up a series of packages that provide
these commands and do most of the work of typography automatically.
1.1 Document class
Every LaTeX document has a document class. Think of it like the CSS to the Résumé’s HTML. Classes tells LaTeX how to layout the document and there are a few builtin ones like ‘article’ that work fairly well for basic document. I’ve built up my own class that works well for résumés. It’s largely based off of @posquit0’s Awesome-CV but with some minor adjustments to make it a little cleaner. In addition, I’ve followed some of Matthew Butterick’s guide on résumés. You can read about them at Practical Typography.
I’ve make it available at resume.cls of this repo’s root but I won’t include it in this blog post (but it’s still in the ./README.html file).
1.2 Header
Every LaTeX document has a document class. For this document, I will use a custom resume class.
\documentclass[11pt]{resume}
Next, I’ll provide some semantic information. Each of these commands is defined in the résumé class. You can make your own commands but I’ve tried to make this document as simple as possible.
All of the following elements correspond to my name, address, mobile, email and homepage. The contents are not parsed by LaTeX so you could put anything here, but you should aim to make each command as semantic as possible.
My personal info follows,
\name{Matthew}{Bauer} \address{10118 GARNETT ST, OVERLAND PARK KS 66214} \mobile{(913) 671 0636} \email{mjbauer95@gmail.com} \homepage{www.matthewbauer.us}
This begins the document. The command makecvheader
is from resume.cls and
will setup the basic template.
\begin{document} \makecvheader[C]
Everything that follows is my own résume, but the ideas should be fairly self-explanatory.
1.3 Education
This LaTeX class defines \cvsection
to separate sections of the résumé by a
horizontal line and some blank space. The only argument for \cvsection
sets
the contents of the section header.
I start my résume with the ‘Eduction’ section.
\cvsection{Education} \begin{cventries}
Each cvsection
is composed of multiple cventries
.
\cventry {University of Kansas} {Lawrence, KS, USA} {B.S. in Computer Science} {Aug. 2015 – Exp. Dec. 2018} { \begin{cvitems} \item { Participated in ACM and hackathon competitions } \item { Coursework includes Software Engineering, Programming Languages, and Communication Networks } \end{cvitems} }
cventry
can be though of as a function that takes a variable 5 arguments.
The first will be the heading used. The second will be on the right-hand side
in italics. The third will be in italics immediately below the first. The
fourth will be on the right-hand side of the third argument. The last argument
provides items for the entry.
For my résumé, I have chosen to use each of these arguments for certain purposes. It’s not necessary to do the same, but you should use a consistent style throughout your résumé.
left | right |
---|---|
argument 1 | argument 2 |
argument 3 | argument 4 |
For me, they correspond to,
left | right |
---|---|
Company or University | Location |
Position or degree | Start date - end date |
\end{cventries}
The next section has work experience. It follows the same pattern outlined in the Education section.
1.4 Work Experience
\cvsection{Work Experience} \begin{cventries}
\cventry {Amazon.com, Inc.} {Seattle, WA, USA} {SDE Intern} {Summer 2017} { \begin{cvitems} \item { Worked on Mobile Identity team which manages the login screens for Amazon apps } \item { Project made it easier for teams to register new devices through Identity Services } \item { Used Agile development principles in design and development of project } \end{cvitems} }
\cventry {Lexmark Enterprise Software} {Lenexa, KS, USA} {Software Engineer Intern} {Summer 2015, Summer 2016} { \begin{cvitems} \item { Worked on the Client Architecture team which builds the JavaScript web framework which other teams use to build enterprise solutions } \item { Participated in high level design decision conversations } \item { Project moved the web framework away from in-house solutions to better maintained open source projects while preserving legacy compatibility } \item { Asked to return after impressive first year } \end{cvitems} }
\cventry {Together+Clinic} {Lincoln, NE, USA} {Design Studio Intern} {Spring 2015} { \begin{cvitems} \item { Startup building web app to let doctors track patients recovering from surgery without frequent checkup visits } \item { The web interface is used by both patients to record progress and doctors to track progress } \item { Team used Scrum development principles for quick response and user focused design } \end{cvitems} }
Here we end cventries
.
\end{cventries}
1.5 Honors & Awards
Again we must define a new section, this time for honors and awards.
\cvsection{Honors \& Awards} \begin{cvhonors}
\cvhonor
{3rd Place}
{JayHacks Hackathon}
{Lawrence, KS, USA}
{2017}
\cvhonor
{Grand Prize}
{Google Code-in}
{Mountain View, CA, USA}
{2013}
\end{cvhonors}
1.6 Footer
The makecvfooter
command gives a nice footer that will be put at the bottom
of each page. This can give us the document title and page numbering. In
addition, the LastPage command will tell us how many pages there are in case
we misplace a page while printing.
\makecvfooter {BAUER} {\thepage} {\pageref{LastPage}}
\end{document}
2 Building it
Nix makes it possible to make this Résumé truly reproducible. Nix is a purely functional package manager. This means that each package is defined in a functional language and we have much more powerful tools at our disposal.
Nix can be installed on both Linux and macOS machines. It is fairly easy to setup, provided you have sudo access. Run the following and follow some simple steps to get Nix working,
curl https://nixos.org/nix/install | sh
More information on Nix is available from the Nix homepage. On the next page, I’ll explain how build this résumé using Nix.
2.1 resume.nix
To start, we’ll this need to pull in Nixpkgs. Nixpkgs provides a set of packages for Nix to use. Because Nix is functional, we’ll make nixpkgs an optional argument if we ever want to work with multiple package set versions.
{nixpkgs ? <nixpkgs>}: with import nixpkgs {};
This syntax may be a little hard to understand for users new to Nix. {}:
declares a function. This particular function will take up the entire file and
Nix will autocall
it when no arguments are necessary. This particular
function has one arguments, nixpkgs, that refers to the package set being
used. To make things easier we provide a default after the ?
symbol.
<nixpkgs>
refers to the nixpkgs channels that the user has setup. It can be
updated with,
nix-channel --update
Giving us a potentially newer version of Nixpkgs and its software to work with.
Almost everything in Nix is a derivation (including Nix itself). Each derivation has its own store path so we can reference it through
stdenv.mkDerivation { name = "resume"; src = ./.;
We’ll name this derivation resume and tell it to use the files in the current directory as source.
buildInputs = [ (texlive.combine { inherit (texlive) scheme-basic xetex xetex-def setspace fontspec chktex enumitem xifthen ifmtarg filehook upquote tools ms geometry graphics oberdiek fancyhdr lastpage xcolor etoolbox unicode-math ucharcat sourcesanspro tcolorbox pgf environ trimspaces parskip hyperref url euenc collection-fontsrecommended; }) ];
Inputs in Nix are similar to dependencies in other package managers. Here, we
list only one dependency which provides our LaTeX distribution.
texlive.combine
is a function that produces a derivation which will provide
the xetex
binary. Each attribute listed in between {
and }
will be
passed as LaTeX packages to TeX Live. The inherit
keyword tells Nix to pass
everything after (texlive)
as attributes of texlive
to texlive.combine
.
Each one of those names listed should correspond to TeX Live packages that are
needed to build the résumé PDF.
In the future, I’d like to get Tex Live to actually recognize the packages we are using within LaTeX, but nothing seems to exist to do this.
buildPhase = '' xelatex -file-line-error -interaction=nonstopmode resume.tex '';
Here we actually build the xelatex
file. These options make it easier to
debug xelatex
when something goes wrong and makes sure we don’t get
xelatex
doesn’t require any user input. It will produce a file called
resume.pdf
that we can use as a résumé.
installPhase = '' cp resume.pdf $out '';
Finally, we copy this résumé to $out
where the derivation will live.
}
2.2 Running the build
This entire document is built with ~org-mode~’s Babel engine. This means that we can generate the files needed to build the résumé from scratch. To do this, first we must clone this repository (if you haven’t already).
git clone https://github.com/matthewbauer/resume
cd resume
Next, we need to open this file in Emacs and generate the files (tangle it in Babel lingo). Run this now, if you haven’t already,
emacs README.org
Finally, let’s build these files. From Emacs, type the following: C-c C-v t
(org-babel-tangle). This will take a little bit, but at the end of it you will
have all of the files tangled inside README.org. You can build the résumé
with,
nix-build resume.nix
3 Automating it
Sadly, Nix does not understand raw Org mode (yet). We need a bootstrap to
generate a Nix script from this file to truly automate this. I’ve included it
here for completeness, but you’ll need to generate it first before Nix will
work. If you haven’t already, generate this in org-mode
by moving the cursor
into the src block below and pressing C-u C-c C-v t
(org-babel-tangle).
Alternatively, I’ve provided a pregenerated file at ./default.nix.
{nixpkgs ? <nixpkgs>}: with import nixpkgs {}; let
Again, we’re be defining a function. Now, we will be using the let…in syntax to define a derivation to use.
README = stdenv.mkDerivation { name = "README"; unpackPhase = "true"; buildInputs = [ emacs ]; installPhase = '' mkdir -p $out cd $out cp -r ${./fonts} fonts cp ${./README.org} README.org emacs --batch -l ob-tangle --eval "(org-babel-tangle-file \"README.org\")" cp resume.nix default.nix ''; };
The README derivation builds all of the things contained within this README.org file. Almost every code block here will make a file that we’ll feed into Nix. Fonts are external to the README because they are binary and cannot be put in an Org file, but you can view them in ./fonts.
Now, we’ll create another derivation with Nix. This will utilize a little
known feature of Nix called IFD.
It might not make sense right now, but it runs the README
derivation’s
resume.nix
file as its own Nix expression.
IFD stands for Import From Derivation. Basically, it means we can import
data generated in one derivation, README
, in Nix to generate another
derivation. This will bootstrap the ./README.html and allow us to avoid
generated files.
in import README {inherit nixpkgs;}
This can be thought of as a recursive call to Nix. It basically lets us use
the Nix output of README.org as an input for Nix. The derivation will produce
a ./result
file that will contain the output of build.nix
for the Building
it section.
Now, we can finally build the Résumé! To do this, we just need to run
nix-build
from the command line.
nix-build
Look at ./result
and it will be PDF file you can open.
3.1 Continous Integration
Travis makes it easy to run continuous integration on our résumé.
language: nix
Travis supports Nix projects out-of-the-box so all that’s really needed is the above. However, matrices are useful to make sure it runs on more than one machine and accross different versions.
script: nix-build --arg nixpkgs "builtins.fetchTarball \"$NIXPKGS\""
This line tells Travis what to build. The $NIXPKGS
variable should become
clear after reading the usage below.
We want to target Linux and macOS. This will make sure the build script is portable. Unfortunately, Nix does not support Windows systems and Travis does not support any BSDs.
os:
- linux
- osx
Each value of NIXPKGS
corresponds to a tarball release of Nixpkgs. This
means that we can avoid problems that arise when the Nixpkgs repo is broken.
Any URL to a tarball with a Nixpkgs set will work, but in Nix we call these
‘channels’. Each channel has a set of tests that are required to run before a
new release of the channel. You could actually point directly to Nixpkgs HEAD
using GitHub’s archive URL but this would constantly break as you would
frequently have to rebuild whole packages when you could just let Hydra do it
for you. You can find a whole listing at NixOS.org but I have included
the most recent channels below.
env:
- NIXPKGS=nixos.org/channels/nixos-17.09/nixexprs.tar.xz
- NIXPKGS=nixos.org/channels/nixpkgs-17.09-darwin/nixexprs.tar.xz
- NIXPKGS=nixos.org/channels/nixos-unstable/nixexprs.tar.xz
- NIXPKGS=nixos.org/channels/nixpkgs-unstable/nixexprs.tar.xz
Travis works with ‘matrices’ meaning that every attribute of one property (os) will get crossed with another property (env). We need to modify some of these to get a working matrix.
matrix:
We exclude some (os, env) pairs here. It doesn’t really make sense to use a Darwin channel on Linux or a NixOS channel on macOS.
exclude: - os: linux env: NIXPKGS=nixos.org/channels/nixpkgs-17.09-darwin/nixexprs.tar.xz - os: osx env: NIXPKGS=nixos.org/channels/nixos-17.09/nixexprs.tar.xz - os: osx env: NIXPKGS=nixos.org/channels/nixos-unstable/nixexprs.tar.xz
We allow some failure for unstable branches. We don’t expect stable releases to always work.
allow_failures: - env: NIXPKGS=nixos.org/channels/nixos-unstable/nixexprs.tar.xz - env: NIXPKGS=nixos.org/channels/nixpkgs-unstable/nixexprs.tar.xz
This will end up building five résumés three on Linux machines and two on
macOS machines. So, using ./.travis.yml, you can make Travis automatically
build a resume.pdf
every time you commit a change. You should be able to
set this up yourself but alternatively you can look at my Travis dashboard.
4 Conclusion
More information on reproducible research is available at Reproducible Research. My hope is that eventually more things will become ‘reproducible’. Technologies like Nix and Babel make this fairly easy but they have not yet entered into the average Software Developer’s toolbelt. Reproducible projects may take longer to setup, but they lead to more robust software systems.
Résumé require a careful mix of informative content and flashy styling. Too much information and employers will be overwhelmed but too little and employers assume you are inexperienced. Likewise, . Perhaps eventually, software companies will read through résumés in org-mode instead of PDFs, but alas Silicon Valley has not yet reached this level of Nirvana.
I welcome everyone to fork the repo containing these files. You should be able to generate your own Résumé by modifying the contents of Semantic info and LaTeX document. Any contributions to the process of reproducible résumés are welcome and you can open them as issues under that GitHub repo. Alternatively, you can email me at mjbauer95@gmail.com.