DEV Community

Cover image for Makefile recipes I
Alvaro Cabrera Durán
Alvaro Cabrera Durán

Posted on • Edited on

Makefile recipes I

I've been playing around Makefiles for a while, and I'm still pleased with the outcome you can achieve as task runner.

These Makefile recipes are being used on my own template for frontend development: plate.

Current working directory

It's important to know where we are, final paths are relative from here.

PWD=$(shell pwd)

Defaults

Configuration below is for deployment to GitHub Pages:

src=build
from=master
target=gh-pages
message=Release: $(shell date)

Templates

Path substitution is used to rebase some paths:

  • page:example.md becomes src/pages/example.md
  • css:home.less becomes src/resources/styles/home.less
  • js:app.js becomes src/resources/scripts/app.js
  • app:components:App.svelte becomes src/app/components/App.svelte
  • lib:helpers:api.js becomes src/lib/helpers/api.js
  • res:styles:reset.css becomes src/resources/styles/reset.css
_src=src/$(patsubst js%,resources/scripts%,\
  $(patsubst css%,resources/styles%,\
  $(patsubst res%,resources%,\
  $(patsubst page%,pages%,$(NAME)))))
_path=$(patsubst %/,%,$(_src))
_basedir=$(dir $(_path))

Directories

Those paths are used when creating needed directories and file.

dirname=$(patsubst %/,%,$(_basedir))
filepath=$(patsubst $(_basedir),,$(_path))

Environment vars

Standard variables for the underlying processes.

I'm getting GIT_REVISION to render a <meta name="revision" content="ef825dc"> tag in generated pages.

GIT_REVISION=$(shell git rev-parse --short=7 HEAD)
NODE_ENV=development

export NODE_ENV GIT_REVISION

Targets

A phony target is one that is not really the name of a file; rather it is just a name for a recipe to be executed when you make an explicit request. There are two reasons to use a phony target: to avoid a conflict with a file of the same name, and to improve performance.

.PHONY: ? add rm dev test deps clean prune dist pages deploy

Utils

The iif(...) helper is used to run a command conditionally, its output depends of the returned exit-code.

define iif
  @(($1 > /dev/null 2>&1) && printf "\r* $2\n") || printf "\r* $3\n"
endef

Input

BODY is used for adding files, when empty it'll fallback to STDIN instead.

ifeq ($(BODY),)
BODY := $(shell bash -c 'if test ! -t 0; then cat -; fi')
endif

Validation

The check_defined(...) helper is used to validate INPUT when required.

check_defined = $(strip $(foreach 1,$1, $(call __check_defined,$1,$(strip $(value 2)))))
__check_defined = $(if $(value $1),, $(error $2, e.g. $1=test))

Display all targets in this file

If you run make without arguments this target will be invoked — shows a list of available tasks and usage examples.

?: Makefile
  @awk -F':.*?##' '/^[a-z\\%!:-]+:.*##/{gsub("%","*",$$1);gsub("\\\\",":*",$$1);printf "\033[36m%8s\033[0m %s\n",$$1,$$2}' $<
  @printf "\n  Examples:"
  @printf "\n    make add:page NAME=example.md BODY='# It works!'"
  @printf "\n    make rm:Dockerfile"
  @printf "\n    make clean dev"
  @printf "\n\n"

Adding files to the project

Write new files with make add — where templates add NAME=js/app.js and add:js:app.js are equivalent.

add: ## Create files, scripts or resources
  @$(call check_defined, NAME, Missing file name)
  @$(call check_defined, BODY, Missing file content)
  @mkdir -p $(PWD)/$(dirname)
  @echo $(BODY) > $(PWD)/$(filepath)
  @printf "\r* File $(filepath) was created\n"

add\:%: ## Shortcut for adding files
  @make -s add NAME=$(subst :,/,$*)/$(NAME) BODY=$(BODY)

Remove files from the project

Delete existing files with make rm — where templates rm NAME=css/home.less and rm:css:home.less are equivalent.

rm: ## Remove **any** stuff from your workspace
  @$(call check_defined, NAME, Missing file name)
  @$(call iif,rm -r $(PWD)/$(filepath),File $(filepath) was deleted,Failed to delete $(filepath))
  @$(call iif,rmdir $(PWD)/$(dirname),Parent directory clear,Parent directory is not empty...)

rm\:%: ## Shortcut for removing files
  @make -s rm NAME=$(subst :,/,$*)/$(NAME)

Development tasks

Start your development workflow with the make dev task.

dev: deps ## Start development
  @npm run dev

Testing tasks

Fire your testing workflow with the make test task.

test: deps ## Test for syntax issues
  @npm run check

Build task

Run your build workflow with the make dist task.

dist: deps ## Compile sources for production
  @NODE_ENV=production npm run dist -- -f

Check dependencies

Helpful task to validate if node_modules are already present and ready.

deps: ## Check for installed dependencies
  @(((ls node_modules | grep .) > /dev/null 2>&1) || npm i) || true

Cleanup

Remove cache files from tooling, also deletes the build directory.

The .tarima file is a cached JSON from my tooling.

clean: ## Remove cache and generated artifacts
  @$(call iif,rm -r $(src),Built artifacts were deleted,Artifacts already deleted)
  @$(call iif,unlink .tarima,Cache file was deleted,Cache file already deleted)

Clean dependencies

Ensure node_modules are completely removed from your project.

prune: clean ## Remove all stuff from node_modules/*
  @printf "\r* Removing all dependencies... "
  @rm -rf node_modules/.{bin,cache}
  @rm -rf node_modules/*
  @echo "OK"

GitHub Pages branch

In order to push to gh-pages the branch should exists already.

pages: ## Fetch or create the target branch
  @(git fetch origin $(target) 2> /dev/null || (\
    git checkout --orphan $(target);\
    git rm -rf . > /dev/null;\
    git commit --allow-empty -m "initial commit";\
    git checkout $(from)))

Deployment to GitHub Pages

Build your files and push gh-pages changes to master.

deploy: pages ## Prepare and push changes on target branch
  @(mv $(src) .backup > /dev/null 2>&1) || true
  @(git worktree remove $(src) --force > /dev/null 2>&1) || true
  @(git worktree add $(src) $(target) && (cp -r .backup/* $(src) > /dev/null 2>&1)) || true
  @cd $(src) && git add . && git commit -m "$(message)" || true
  @(mv .backup $(src) > /dev/null 2>&1) || true
  @git push origin $(target) -f || true

Top comments (0)