Setting up LaTeX locally (Mac)

I have been using LaTeX locally for quite some time, I'm not sure how common this is outside India, but I haven't seen it around me as much. It works like a charm for me. Also the script in texcompile is too useful to not share.

Table of Contents

Why?

First, this article is necessary from my experience in BITS. Basically no one uses LaTeX locally. To be fair it's also not instrumental for most people, for e.g., assignments are not required to be typed. In the cases that it is used, like paper-writing or assignment creation (if you're a TA), overleaf is necessary because of the collaborative aspect.

However, I have been stuck in a few cases (flights, buses, etc.) with limited connectivity where I wanted to write and view tex files. Also because no one had done this, I wanted to do it as a fun thing. Apart from this initial motivation, I would strongly suggest using local LaTeX for anything where you don't have to collaborate: posters, notes, assignments, etc. The latency difference is massive. Overleaf can take about 3 seconds per compile (from when you hit ⌘ + S and the new content becomes visible), this can get worse if you have bad network connection, or if the server is crowded right before a deadline. Whereas my local setup can compile the same in a few ms. This basically means you spend no time at all waiting.

Finally, overleaf is maintained really well but it is still a system that could fail. If a downtime happens right before some deadline (highly unlikely), it could be a pretty nasty experience.

Setup

The setup is quite simple, just a single command to install basictex.

            brew install --cask basictex
        
This will give you access to most things required. You can check your installation with
            
            pdflatex --version
            bibtex --version
            tlmgr --version
            
        
If you need any other package, the default command is
            sudo tlmgr install <package_name>
        
This installation is about 150MB whereas the whole MacTex installation can take a couple of GBs. I edit LaTeX in VSCode, and there is a simple extension for syntax highlighting. This has some issues, for e.g., $ will not autoclose!

TexLive releases an update every year, basictex makes it really easy to update the whole system,

            sudo tlmgr update --self
        
Then you can run a similar command
            sudo tlmgr update --all
        
to update all the packages.

texcompile

I saved the following script and made it an executable, and then sourced it in my .zshrc.

Expand (quite big)
            
#!/bin/bash
# ~Courtesy of Claude 3.7~

texcompile() {
    # Default values
    local file="main"
    local bibengine="default"
    local quick=false
    local clean=false
    local verbose=false
    local help=false
    local open=false

    # Parse arguments
    while [[ $# -gt 0 ]]; do
        case "$1" in
            -f|--file)
                file="${2%.tex}"  # Remove .tex extension if present
                shift 2
                ;;
            --no_bib)
                # No bibliography processing
                if [[ "$bibengine" == "default" ]]; then
                    bibengine=""
                else
                    echo "Please pass only one of --no_bib, --bibtex, --biblatex"
                    return 1
                fi
                shift
                ;;
            --bibtex)
                if [[ "$bibengine" == "default" ]]; then
                    bibengine="bibtex"
                else
                    echo "Please pass only one of --no_bib, --bibtex, --biblatex"
                    return 1
                fi
                shift
                ;;
            --biblatex)
                if [[ "$bibengine" == "default" ]]; then
                    bibengine="biber"
                else
                    echo "Please pass only one of --no_bib, --bibtex, --biblatex"
                    return 1
                fi
                shift
                ;;
            -q|--quick|--update)
                quick=true
                shift
                ;;
            -c|--clean)
                clean=true
                shift
                ;;
            -v|--verbose)
                verbose=true
                shift
                ;;
            -p|--open)
                open=true
                shift
                ;;
            -h|--help)
                help=true
                shift
                ;;
            *)
                # If no flag is provided, assume it's the filename
                if [[ "$1" != -* ]]; then
                    file="${1%.tex}"  # Remove .tex extension if present
                    shift
                else
                    echo "Unknown option: $1"
                    help=true
                    shift
                fi
                ;;
        esac
    done

    # Display help
    if $help; then
        echo "Usage: texcompile [options] [filename]"
        echo "Options:"
        echo "  -f, --file FILENAME     Specify the LaTeX file to compile (without .tex extension)"
        echo "  --no_bib                No references in the file (will not run bibtex or biblatex)"
        echo "  --bibtex                Use BibTeX for bibliography (default)"
        echo "  --biblatex              Use Biber for bibliography"
        echo "  -q, --quick, --update   Quick compilation (only run pdflatex once)"
        echo "  -c, --clean             Clean auxiliary files after compilation"
        echo "  -v, --verbose           Show detailed output"
        echo "  -p, --open              Open the PDF after compilation"
        echo "  -h, --help              Show this help message"
        return 0
    fi

    # Check if file exists (with or without extension)
    if [[ ! -f "${file}.tex" ]]; then
        echo "Error: ${file}.tex not found."
        return 1
    fi

    echo "Compiling ${file}.tex..."

    # print a message if bibengine is empty
    if [[ -z "$bibengine" ]]; then
        echo "No bibliography processing will be performed (only 2 pdflatex runs)."
    fi

    # if the bibengine is none, i.e., not specified, then use bibtex by default
    if [[ "$bibengine" == "default" ]]; then
        bibengine="bibtex"
    fi

    # Function to run a command with or without verbose output
    run_cmd() {
        if $verbose; then
            eval "$@"
        else
            eval "$@ > /dev/null 2>&1"
        fi
        return $?
    }

    # Quick compilation (single run)
    if $quick; then
        if ! run_cmd "pdflatex ${file}"; then
            echo "Error: pdflatex compilation failed."
            return 1
        fi
    else
        # Full compilation (multiple runs)
        if ! run_cmd "pdflatex ${file}"; then
            echo "Error: First pdflatex run failed."
            return 1
        fi

        # Process bibliography
        if [[ -n "$bibengine" ]]; then
            if grep -q "\\\\bibliography" "${file}.tex" || grep -q "\\\\addbibresource" "${file}.tex"; then
                echo "Processing bibliography with $bibengine..."
                if [[ "$bibengine" == "bibtex" ]]; then
                    if ! run_cmd "bibtex ${file}"; then
                        echo "Error: bibtex processing failed."
                        return 1
                    fi
                else  # biber
                    if ! run_cmd "biber ${file}"; then
                        echo "Error: biber processing failed."
                        return 1
                    fi
                fi
            fi

            # Additional runs to resolve references
            if ! run_cmd "pdflatex ${file}"; then
                echo "Error: Second pdflatex run failed."
                return 1
            fi
        fi

        if ! run_cmd "pdflatex ${file}"; then
            echo "Error: Final pdflatex run failed."
            return 1
        fi
    fi

    # Clean auxiliary files if requested
    if $clean; then
        echo "Cleaning auxiliary files..."
        rm -f "${file}.aux" "${file}.log" "${file}.out" "${file}.toc" "${file}.lof" "${file}.lot"
        rm -f "${file}.bbl" "${file}.blg" "${file}.bcf" "${file}.run.xml" "${file}.synctex.gz" "${file}.brf"
        rm -f "${file}.nav" "${file}.snm" "${file}.vrb"  # Beamer-specific files
    fi

    # Check if PDF was created successfully
    if [[ -f "${file}.pdf" ]]; then
        echo "Successfully compiled ${file}.pdf"

        # Open the PDF if requested
        if $open; then
            if command -v open &> /dev/null; then
                open "${file}.pdf"
            else
                echo "Could not find a suitable command to open the PDF."
            fi
        fi
    else
        echo "Error: Failed to generate PDF file."
        return 1
    fi

    return 0
}
            
        

You can check all flags by running

            texcompile -h
        
For any project that I want to compile, I will first do a run of
            texcompile <fname> # (.tex is optional) 
        
This might take a couple of seconds and creates all the auxiliary files, resolves cross references and citations, the sequence is
             pdflatex -> bibtex / biblatex -> pdflatex -> pdflatex
        
Once this is run, until a new citation or a cross reference is added, you only need to run a single pdflatex, and the generated auxiliary files are enough to resolve references and citations, i.e., I use
            texcompile -q <fname>
        
Finally once you are happy and have made the edits, do a final run with
            texcompile -p -c <fname>
        
This will delete all the auxiliary files, and open the PDF for final inspection.

Finally, to get an overleaf like experience, you can also add this command as a shortcut in VSCode, though I think running it this way is more controllable. Sometimes (or most times in the start), there might be several missing packages, if the compilation is stuck, I terminate it with Ctrl+d, and then run

            texcompile -v
        
This will be verbose and show the error.