This weekend, my roommate asked me to make a website for him. I wrote it in PHP so I could just make some templates and he could import them, but it bothered me that I was rendering a static website with PHP. The easiest solution seemed to be running every file though PHP before uploading the site, so I needed a Makefile.

The Makefile

PROJECT := example
PACKAGE := $(PROJECT).tar.gz

# Get all content/*.php and convert to ./*.html
HTML_FILES := $(patsubst content/%.php, %.html, $(wildcard content/*.php))

# Get all js/*.js and convert to js/*-min.js
JAVASCRIPT_FILES := $(patsubst %.js, %-min.js, $(filter-out %-min.js, $(wildcard js/*.js)))

# Get all css/*.css and convert to *-min.css
CSS_FILES := $(patsubst %.css, %-min.css, $(filter-out %-min.css, $(wildcard css/*.css)))

RED := \033[31m
GREEN := \033[32m
CLEAR := \033[0m

.PHONY: all
.PHONY: clean
.PHONY: dist

all: $(HTML_FILES) $(JAVASCRIPT_FILES) $(CSS_FILES) ;

clean:
        rm -f $(HTML_FILES) $(JAVASCRIPT_FILES) $(CSS_FILES) $(PACKAGE)

dist: $(PACKAGE) ;

$(PACKAGE): $(HTML_FILES) $(JAVASCRIPT_FILES) $(CSS_FILES)
        @tar -caf $(PACKAGE) $(HTML_FILES)
        @echo -e "Built package $(RED)$@$(CLEAR)"

%.html: index.php content/%.php
        @echo -e "Generating $(GREEN)$@$(CLEAR) from $<"
        @php index.php --page=$(<:%.php=%) > $@
        @tidy -m -utf8 -q $@

%-min.js: %.js
        @echo -e "Generating $(GREEN)$@$(CLEAR) from $<"
        @yuicompressor -o $@ $<

%-min.css: %.css
        @echo -e "Generating $(GREEN)$@$(CLEAR) from $<"
        @yuicompressor -o $@ $<

There's quite a bit in this file that was new to me, so I figured it might be helpful for my loyal readers to explain it.

Implicit Rules

Implicit rules are any Make file that involves a wildcard. The wildcard in this case is %. For example, this rule will be used any time we need to compile .c files into .o files:

%.o : %.c
        gcc -o $@ $<

Notice that it reads like a normal rule, just using a wildcard: "When you need any .o file, create it from a .c file with the same name". Also notice the automatic variables, $@ and $<. $@ is the target (the file to be created) and $< is the name of the first target (the file to make it out of). There are also other automatic variables, but these were the most useful to me.

Functions

GNU Make comes with several built-in functions. You call them in this form:

$(function_name, param1, param2, etc)

The text functions were the ones I used most, like subst and patsubst (terribly named), which are variations on find/replace, and filter and filter-out (better names). I could imagine the others being useful too (strip, sort, etc.). There are also several other kinds of function whichs seem useful (see the link).

.PHONY targets

The way Make works is by checking the target file's timestamp and rebuilding if it's old. This becomes a problem if you don't actually create the target file — for example, with the all, clean and dist targets. You don't create an "all" file, it's just a name. Make's way of dealing with this is phony targets, which basically tells it "the target isn't actually a file", so it doesn't check that file, it just always trys to build. This has two advantages:

  • It's very slightly faster (because it's not checking for a file that should never exist).
  • It's slightly safer (because if that file every does exist, it'll still work right.

Using a phony target looks like this:

.PHONY : all
.PHONY : clean
.PHONY : dist

Empty targets

This is a minor one, but if you want an empty target, just end it with a semi-colon (;):

all: $(DEPENDS) ;

And that's all for today's show. Tune in next week.