Skip to content

Latest commit

 

History

History
1029 lines (856 loc) · 31.2 KB

README.org

File metadata and controls

1029 lines (856 loc) · 31.2 KB

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{4200 W 54th Ter, Roeland Park, KS 66205}
\mobile{+1 (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.

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é.

leftright
argument 1argument 2
argument 3argument 4

For me, they correspond to,

leftright
Company or UniversityLocation
Position or degreeStart 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
  {Mercury}
  {Remote}
  {Software Engineer}
  {January 2021 – Present}
  {}
   \cventry
     {Obsidian Systems}
     {New York, NY, USA}
     {Software Engineer}
     {June 2018 – December 2020}
     {
	\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 { Moved web framework away from in-house solutions to better
	    maintained open source projects while preserving legacy
	    compatibility }
	\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 mjbauer95@gmail.com.