Lecture 15
build tool for the creation of software / libraries / documents by specifying dependencies
Originally created by Stuart Feldman in 1976 at Bell Labs
Almost universally available (all flavors of unix / linux / MacOS / Windows via RTools)
Dependencies are specified using a text-based Makefile with a simple syntax
A Makefile provides a list of target files along, their dependencies, and the steps necessary to generate each of the targets from the dependencies.
In the above example target* and depend* are all just files (given by a relative or absolute path).
Because the Makefile specifies the dependency structure make knows when a file has changed (by examining the file’s modification timestamp) and only runs the steps that depend on the file(s) that have changed.
After running make the first time, I edit paper.Rmd, what steps run if I run make again?
What about editing fig1/fig.R?
Like R or other language we can define variables
By default if you run make without arguments it will attempt to build the first target in the Makefile (whose name does not start with a .). By convention we often include an all target which explicitly specifies how to build everything within the project.
all is an example of what is called a phony target - because there is no file named all in the directory. Other common phony targets:
clean - remove any files created by the Makefile, restores to the original state
install - for software packages, installs the compiled programs / libraries / header files
Optionally, we specify all phony targets by including a line with .PHONY as the target and the phony targets as dependencies, i.e.:
$@ - the file name of the target
$< - the name of the first dependency
$^ - the names of all dependencies
$(@D) - the directory part of the target
$(@F) - the file part of the target
$(<D) - the directory part of the first dependency
$(<F) - the file part of the first dependency
Often we want to build several files in the same way, in these cases we can use % as a special wildcard character to match both targets and dependencies.
So we can go from
to
all: hw4.html
hw4.html: hw4.qmd data/lq.rds data/dennys.rds
    quarto render hw4.qmd
data/lq.rds: parse_lq.R data/lq/*.html
    Rscript parse_lq.R
data/lq/*.html: get_lq.R
    Rscript get_lq.R
data/dennys.rds: parse_dennys.R data/dennys/*.html
    Rscript parse_dennys.R
data/dennys/*.html: get_dennys.R
    Rscript get_dennys.R
clean:
    rm -f hw4.html
    rm -rf data/
.phony: all cleanmake is great, but has some limitations for data analysis pipelines:
The targets package provides a modern R-native alternative:
A targets pipeline is defined in a _targets.R file:
Run the pipeline:
# _targets.R
library(targets)
list(
  # Get La Quinta data
  tar_target(lq_html_files, {
    source("get_lq.R")
    list.files("data/lq", pattern = "*.html", full.names = TRUE)
  }),
  tar_target(lq_data, {
    source("parse_lq.R")
    readRDS("data/lq.rds")
  }, depend_on = lq_html_files),
  # Get Denny's data
  tar_target(dennys_html_files, {
    source("get_dennys.R")
    list.files("data/dennys", pattern = "*.html", full.names = TRUE)
  }),
  tar_target(dennys_data, {
    source("parse_dennys.R")
    readRDS("data/dennys.rds")
  }, depend_on = dennys_html_files),
  # Render final report
  tar_quarto(hw4, "hw4.qmd")
)Sta 523 - Fall 2025