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
becomessrc/pages/example.md
-
css:home.less
becomessrc/resources/styles/home.less
-
js:app.js
becomessrc/resources/scripts/app.js
-
app:components:App.svelte
becomessrc/app/components/App.svelte
-
lib:helpers:api.js
becomessrc/lib/helpers/api.js
-
res:styles:reset.css
becomessrc/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)