Makefile installer example

This is part of the Semicolon&Sons Code Diary - consisting of lessons learned on the job. You're in the unix category.

Last Updated: 2024-04-24

I'm going to annotate with comments and contrast with Homebrew

LIB_FOLDER=share/reddio
# 1. Showcases how shell commands can be run to fill out variables. This
# one ends up containing the 10 or so files that the package uses.
# 2. The ignoring of STDERR is presumably there to prevent error logs
# from being interpreted as file names put into LIB_FILES.
LIB_FILES=`find "$(LIB_FOLDER)" -type f 2>/dev/null`

# 1. Set PREFIX _only_ if not already set. At first this seems odd, since at
# this point we've never encountered PREFIX yet. However it anticipates calling
# make file with an override:
# `PREFIX="$HOME"/.local make install`

# 2. This becomes the installation directory. The installer chooses /usr/local,
# which is the same as homebrew and /usr/local/bin is already on $PATH in most
# systems. Indeed Apple has assigned this directory for non-system utilities.
PREFIX?=/usr/local

# 1. The $(VAR_NAME) syntax interpolates in another variable from the Makefile.
# 2. Why the `share` subdirectory? This seems common practice for storing files for many CLI utilities. Homebrew
# creates folders here for its commands - however these symlink to /usr/local/Cellar, where Homebrew
# stores the actual data.
DOC_FOLDER=$(PREFIX)/share/doc/reddio

# 1. This "target" (what we call each command in the makefile) handles the install
# 2. We copy the entry point script to /usr/local/bin (creating if not there)
# 3. Then we copy and the package files (via a one-line shell loop) to `/usr/local/share/reddit`.
# 4. Note the use of `$$file` to refer to shell variables from the current line. This distinguishes them
# from makefile variales.
install:
    mkdir -p -- "$(PREFIX)/bin"
    cp reddio $(PREFIX)/bin/
    for file in $(LIB_FILES); do cp -- "$$file" "$(PREFIX)/$$file"; done

    mkdir -p -- "$(DOC_FOLDER)"
    cp doc/* "$(DOC_FOLDER)"

uninstall:
    rm -- "$(PREFIX)/bin/reddio"

    for file in $(LIB_FILES); do rm -- "$(PREFIX)/$$file"; done
    rmdir -- "$(PREFIX)/$(LIB_FOLDER)" || :

    for file in doc/*; do rm -- "$(DOC_FOLDER)/$${file#*/}"; done
    rmdir -- "$(DOC_FOLDER)" || :

# The "standard" way of using Make is that each of "targets" is a file that
# should get generated. As a side effect, this meant that this Makefile would
# not run if there happened to be a file named "install" in the directory, since
# it would think its job done. The fix is to declare these targets as "phony" -
# since, in reality, they are recipes here.
.PHONY: install uninstall

Resources