Reproducible résumés
Recently, ‘reproducible’ papers have become popular in Academia. The idea is to make all of the data and code available so that finding can be reproduced in an accessible way. Technologies like org-mode’s literate programming engine, Babel and Nix, the functional package manager, make this kind of thing very easy to do.
This idea hasn’t seen wide used in outside Academia, however. That’s a shame because I find it to be extremely useful in any kind of software projects. To make this idea more accessible for those outside Academia, I’m going to lay out how to apply this to something everyone needs when applying for a job, a résumé. 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.
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.
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.org file).
resume.cls
This is not exported because it’s kind of too long for a blog post.
\ProvidesClass{resume}[2017/08/01 Resume Class]
\NeedsTeXFormat{LaTeX2e}
These commands are necessary to declare any LaTeX class.
\DeclareOption{draft}{\setlength\overfullrule{5pt}}
\DeclareOption{final}{\setlength\overfullrule{0pt}}
\DeclareOption*{
\PassOptionsToClass{\CurrentOption}{article}
}
\ProcessOptions\relax
\LoadClass{article}
We’ll base this class off of the builtin article
class.
\RequirePackage{upquote}
\RequirePackage{setspace}
\RequirePackage{array}
\RequirePackage{enumitem}
\RequirePackage{geometry}
\RequirePackage{fancyhdr}
\RequirePackage{xcolor}
\RequirePackage{ifxetex}
\RequirePackage{xifthen}
\RequirePackage{etoolbox}
\RequirePackage{setspace}
\RequirePackage[quiet]{fontspec}
\RequirePackage{unicode-math}
\RequirePackage[skins]{tcolorbox}
\RequirePackage{parskip}
\RequirePackage[hidelinks,unicode]{hyperref}
Now we require some packages that we’ll need below. Each of these packages will need to be pull in later. This process is explained in the Building it section.
\geometry{left=4.0cm, top=2.0cm, right=4.0cm, bottom=2.0cm, footskip=.5cm}
Now we can setup the basic geometry of the résumé PDF.
\hypersetup{
pdftitle={},
pdfauthor={},
pdfsubject={},
pdfkeywords={}
}
\fancyhfoffset{0em}
\renewcommand{\headrulewidth}{0pt}
\fancyhf{}
\pagestyle{fancy}
Next we setup some more basic metadata stuff…
\defaultfontfeatures{Ligatures=TeX}
\newfontfamily\headerfontspaced{FiraSans}[
Path = fonts/,
UprightFont = *-Regular,
BoldFont = *-Bold,
ItalicFont = *-Italic,
BoldItalicFont = *-BoldItalic,
LetterSpace = 15
]
\newfontfamily\headerfont{FiraSans}[
Path = fonts/,
UprightFont = *-Regular,
BoldFont = *-Bold,
ItalicFont = *-Italic,
BoldItalicFont = *-BoldItalic
]
\newfontfamily\footerfont{FiraSans}[
Path = fonts/,
UprightFont = *-Regular,
BoldFont = *-Bold,
ItalicFont = *-Italic,
BoldItalicFont = *-BoldItalic
]
\newfontfamily\bodyfont{Charter}[
Path = fonts/,
UprightFont = * Regular,
BoldFont = * Bold,
ItalicFont = * Italic,
BoldItalicFont = * Bold Italic
]
This sets up the fonts that we will use. I’ve chosen Fira and Charter to use. They are both open source fonts and also recommended Butterick! You substitute you’re own by changing the name and adding them to the ./fonts directory.
\newcommand*{\headerfirstnamestyle}[1]{
{\fontsize{24pt}{1em}\headerfontspaced\MakeUppercase{#1} }
}
\newcommand*{\headerlastnamestyle}[1]{
{\fontsize{24pt}{1em}\headerfontspaced\MakeUppercase{#1} }
}
\newcommand*{\headersuffixstyle}[1]{
{\fontsize{24pt}{1em}\headerfontspaced #1 }
}
\newcommand*{\headerpositionstyle}[1]{
{\fontsize{7.6pt}{1em}\bodyfont\scshape #1}
}
\newcommand*{\headeraddressstyle}[1]{
{\fontsize{10pt}{1em}\headerfontspaced #1}
}
\newcommand*{\headersocialstyle}[1]{
{\fontsize{8pt}{1em}\headerfont #1}
}
\newcommand*{\headerquotestyle}[1]{
{\fontsize{9pt}{1em}\bodyfont\itshape #1}
}
\newcommand*{\footerstyle}[1]{
{\fontsize{8pt}{1em}\footerfont\scshape #1}
}
\newcommand*{\sectionstyle}[1]{
{\fontsize{8pt}{1em}\headerfont\bfseries\MakeUppercase{#1}}
}
\newcommand*{\subsectionstyle}[1]{
{\fontsize{8pt}{1em}\headerfont\scshape}
}
\newcommand*{\paragraphstyle}{
\fontsize{9pt}{1em}\bodyfont\upshape
}
\newcommand*{\entrytitlestyle}[1]{
{\fontsize{11pt}{1em}\headerfont\bfseries #1}
}
\newcommand*{\entrypositionstyle}[1]{
{\fontsize{8pt}{1em}\bodyfont\itshape #1}}
\newcommand*{\entrydatestyle}[1]{
{\fontsize{8pt}{1em}\bodyfont\slshape #1}
}
\newcommand*{\entrylocationstyle}[1]{
{\fontsize{9pt}{1em}\bodyfont\slshape #1}
}
\newcommand*{\descriptionstyle}[1]{
{\fontsize{9pt}{1em}\bodyfont\upshape #1}
}
\newcommand*{\subentrytitlestyle}[1]{
{\fontsize{8pt}{1em}\bodyfont\mdseries #1}
}
\newcommand*{\subentrypositionstyle}[1]{
{\fontsize{7pt}{1em}\bodyfont\scshape #1}
}
\newcommand*{\subentrydatestyle}[1]{
{\fontsize{7pt}{1em}\bodyfont\slshape #1}
}
\newcommand*{\subentrylocationstyle}[1]{
{\fontsize{7pt}{1em}\bodyfont\slshape #1}
}
\newcommand*{\subdescriptionstyle}[1]{
{\fontsize{8pt}{1em}\bodyfont\upshape #1}
}
\newcommand*{\honortitlestyle}[1]{
{\fontsize{9pt}{1em}\bodyfont #1}
}
\newcommand*{\honorpositionstyle}[1]{
{\fontsize{9pt}{1em}\bodyfont\bfseries #1}
}
\newcommand*{\honordatestyle}[1]{
{\fontsize{9pt}{1em}\bodyfont #1}
}
\newcommand*{\honorlocationstyle}[1]{
{\fontsize{9pt}{1em}\bodyfont\slshape #1}
}
\newcommand*{\skilltypestyle}[1]{
{\fontsize{10pt}{1em}\bodyfont\bfseries #1}
}
\newcommand*{\skillsetstyle}[1]{
{\fontsize{9pt}{1em}\bodyfont #1}
}
Above we setup the basic font size and style to use for different parts of the doument.
\newcommand*{\name}[3]{\def\@firstname{#1}\def\@lastname{#2}\def\@suffix{#3}}
\newcommand*{\firstname}[1]{\def\@firstname{#1}}
\newcommand*{\lastname}[1]{\def\@lastname{#1}}
\newcommand*{\familyname}[1]{\def\@lastname{#1}}
\newcommand*{\suffix}[1]{\def\@suffix{#1}}
\newcommand*{\address}[1]{\def\@address{#1}}
\newcommand*{\position}[1]{\def\@position{#1}}
\newcommand*{\mobile}[1]{\def\@mobile{#1}}
\newcommand*{\email}[1]{\def\@email{#1}}
\newcommand*{\homepage}[1]{\def\@homepage{#1}}
\newcommand*{\extrainfo}[1]{\def\@extrainfo{#1}}
\renewcommand*{\quote}[1]{\def\@quote{#1}}
The above commands are all useful as semantic information.
\newcommand{\acvHeaderAfterNameSkip}{.4mm}
\newcommand{\acvHeaderAfterPositionSkip}{.4mm}
\newcommand{\acvHeaderAfterAddressSkip}{-.5mm}
\newcommand{\acvHeaderSocialSep}{\quad\textbar\quad}
\newcommand{\acvHeaderAfterSocialSkip}{6mm}
\newcommand{\acvHeaderAfterQuoteSkip}{5mm}
\newcommand{\acvSectionTopSkip}{3mm}
\newcommand{\acvSectionContentTopSkip}{2.5mm}
\newcolumntype{L}[1]{
>{\raggedright\let\newline\\\arraybackslash\hspace{0pt}}m{#1}
}
\newcolumntype{C}[1]{
>{\centering\let\newline\\\arraybackslash\hspace{0pt}}m{#1}
}
\newcolumntype{R}[1]{
>{\raggedleft\let\newline\\\arraybackslash\hspace{0pt}}m{#1}
}
\def\vhrulefill#1{\leavevmode\leaders\hrule\@height#1\hfill \kern\z@}
\newcommand*{\ifempty}[3]{\ifthenelse{\isempty{#1}}{#2}{#3}}
More document structuring commands and code….
\newcommand*{\makecvheader}[1][C]{
\newlength{\headertextwidth}
\newlength{\headerphotowidth}
\ifthenelse{\isundefined{\@photo}}{
\setlength{\headertextwidth}{\textwidth}
\setlength{\headerphotowidth}{0cm}
}{
\setlength{\headertextwidth}{0.76\textwidth}
\setlength{\headerphotowidth}{0.24\textwidth}
}
\begin{minipage}[c]{\headertextwidth}
\ifthenelse{\equal{#1}{L}}
{\raggedright}
{\ifthenelse{\equal{#1}{R}}{\raggedleft}{\centering}}
\headerfirstnamestyle{\@firstname}
\headerlastnamestyle{{}\@lastname}
\ifthenelse{\equal{\@suffix}{}}
{}
{\headersuffixstyle{{}\@suffix}}
\\[\acvHeaderAfterNameSkip]
\ifthenelse{\isundefined{\@position}}
{}
{\headerpositionstyle{\@position\\[\acvHeaderAfterPositionSkip]}}
\ifthenelse{\isundefined{\@address}}
{}
{\headeraddressstyle{\@address\\[\acvHeaderAfterAddressSkip]}}
\headersocialstyle{
\newbool{isstart}
\setbool{isstart}{true}
\ifthenelse{\isundefined{\@mobile}}
{}
{
\@mobile
\setbool{isstart}{false}
}
\ifthenelse{\isundefined{\@homepage}}
{}
{
\ifbool{isstart}{\setbool{isstart}{false}}{\acvHeaderSocialSep}
\href{http://\@homepage}{\@homepage}
}
\ifthenelse{\isundefined{\@email}}
{}
{
\ifbool{isstart}{\setbool{isstart}{false}}{\acvHeaderSocialSep}
\href{mailto:\@email}{\@email}
}
} \\[\acvHeaderAfterSocialSkip]
\ifthenelse{\isundefined{\@quote}}
{}
{\headerquotestyle{\@quote\\}\vspace{\acvHeaderAfterQuoteSkip}}
\end{minipage}
}
\newcommand*{\makecvfooter}[3]{
\fancyfoot[C]{\footerstyle{#1 RÉSUMÉ — PAGE #2}}
}
Headers and footers are declared above.
\newcommand{\cvsection}[1]{
\vspace{\acvSectionTopSkip}
\hrule
\sectionstyle{#1}
\phantomsection{}
}
\newcommand{\cvsubsection}[1]{
\vspace{\acvSectionContentTopSkip}
\vspace{-3mm}
\subsectionstyle{#1}
\phantomsection{}
}
\newenvironment{cvparagraph}{
\vspace{\acvSectionContentTopSkip}
\vspace{-3mm}
\paragraphstyle{}
}{
\par
\vspace{2mm}
}
\newenvironment{cventries}{
\vspace{\acvSectionContentTopSkip}
\begin{center}
}{
\end{center}
}
\newcommand*{\cventry}[5]{
\vspace{-2.0mm}
\setlength\tabcolsep{0pt}
\setlength{\extrarowheight}{0pt}
\begin{tabular*}
{\textwidth}
{@{\extracolsep{\fill}} L{\textwidth - 4.5cm} R{4.5cm}}
\entrytitlestyle{#1} & \entrylocationstyle{#2} \\
\entrypositionstyle{#3} & \entrydatestyle{#4} \\
\multicolumn{2}{L{\textwidth}}{\descriptionstyle{#5}}
\end{tabular*}
}
\newenvironment{cvsubentries}{
\begin{center}
}{
\end{center}
}
\newcommand*{\cvsubentry}[4]{
\setlength\tabcolsep{0pt}
\setlength{\extrarowheight}{0pt}
\begin{tabular*}{\textwidth}
{@{\extracolsep{\fill}} L{\textwidth - 4.5cm} R{4.5cm}}
\setlength\leftskip{0.2cm}
\subentrytitlestyle{#2} & \ifthenelse{\equal{#1}{}}
{\subentrydatestyle{#3}}{}
\ifthenelse{\equal{#1}{}}
{}
{\subentrypositionstyle{#1} &
\subentrydatestyle{#3} \\}
\ifthenelse{\equal{#4}{}}
{}
{\multicolumn{2}{L{17.0cm}}{\subdescriptionstyle{#4}} \\}
\end{tabular*}
}
\newenvironment{cvhonors}{
\vspace{\acvSectionContentTopSkip}
\vspace{-2mm}
\begin{center}
\def\arraystretch{1.5}
\setlength\tabcolsep{0pt}
\setlength{\extrarowheight}{0pt}
\begin{tabular*}{\textwidth}
{@{\extracolsep{\fill}} C{1.5cm} L{\textwidth - 6.0cm} R{4.5cm}}
}{
\end{tabular*}
\end{center}
}
\newcommand*{\cvhonor}[4]{
\honordatestyle{#4} & \honorpositionstyle{#1}, \honortitlestyle{#2} &
\honorlocationstyle{#3} \\
}
\newenvironment{cvskills}{
\vspace{\acvSectionContentTopSkip}
\vspace{-2.0mm}
\begin{center}
\setlength\tabcolsep{1ex}
\setlength{\extrarowheight}{0pt}
\begin{tabular*}{\textwidth}
{@{\extracolsep{\fill}} r L{\textwidth * \real{0.9}}}
}{
\end{tabular*}
\end{center}
}
\newcommand*{\cvskill}[2]{
\skilltypestyle{#1} & \skillsetstyle{#2} \\
}
\newenvironment{cvitems}{
\vspace{-4.0mm}
\begin{itemize}[leftmargin=2ex, rightmargin=4.5cm, nosep, noitemsep]
\setlength\itemsep{0.5em}
\setlength{\parskip}{0pt}
\renewcommand{\labelitemi}{\bullet}
}{
\end{itemize}
\vspace{-4.0mm}
}
We now have now defined all of the commands needed to build our LaTeX document.
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{313 E 85th St \#2C, New York, NY 10028}
\mobile{(913) 671 0636}
\email{[email protected]}
\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.
Entries
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.
\begin{cventries}
Each cvsection
is composed of multiple cventries
. 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.
Work Experience
\cvsection{Work Experience}
\begin{cventries}
\cventry
{Obsidian Systems}
{New York, NY, USA}
{Software Engineer}
{June 2018 – Present}
{
\begin{cvitems}
\item { Developed with Reflex framework, a Haskell-based web
framework }
\item { Helped in creating Obelisk, a deployment tool for Reflex }
\item { Worked on Nix expressions used in Reflex and Obelisk }
\end{cvitems}
}
\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 { Made it easier for teams to register new devices through
Identity Services }
\item { Learned enterprise Java programming through the Spring
Framework }
\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 { Moved 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}
}
Here we end cventries
.
\end{cventries}
Education
Education section contains basic information on university or college background.
\cvsection{Education}
\begin{cventries}
\cventry
{University of Kansas}
{Lawrence, KS, USA}
{B.S. in Computer Science}
{Aug. 2015 – Dec. 2018}
{
\begin{cvitems}
\item { Participated in ACM and hackathon competitions }
\item { Coursework includes Software Engineering, Programming Languages,
and Communication Networks }
\end{cvitems}
}
\end{cventries}
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}
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}
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.
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 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.
}
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
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.org 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.
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-18.09/nixexprs.tar.xz
- NIXPKGS=nixos.org/channels/nixpkgs-18.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: linux
env: NIXPKGS=nixos.org/channels/nixpkgs-18.09-darwin/nixexprs.tar.xz
- os: osx
env: NIXPKGS=nixos.org/channels/nixos-18.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.
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 [email protected].